0

I'm trying to create a custom renderer that should produce a vertical Slider. The drag of the thumb of the slider works and the thumb starts to slide perfectly if I run it on a tablet emulator. Sizes are detected perfectly. Than I ran it on a my phone and whoops what's that. The thumb suddenly doesn't function as expected anymore. The problem is in the Width and Height of the parent the Thumb is placed upon. On the table emulator I get the expected Width and Height but on my Phone I get unexpected values.

Are sizes not in pixels on Android? Is there some factor I need to take into consideration.

Below is the Custom Renderer. The problem is in this part:

case MotionEventActions.Move:
                if (touchedDown)
                {
                    if (dragView.DragDirection == DragDirectionTypes.All || dragView.DragDirection == DragDirectionTypes.Horizontal)
                    {
                        var newX = x - dX;

                        if (parent != null)
                        {
                            // The parent.Width isn't what I expect below
                            if (newX + Width > parent.Width) newX = (float)(parent.Width - Width);
                            if (newX < 0) newX = 0;
                        }

                        SetX(newX);
                    }

                    if (dragView.DragDirection == DragDirectionTypes.All || dragView.DragDirection == DragDirectionTypes.Vertical)
                    {
                        var newY = y - dY;

                        if (parent != null)
                        {
                            // The parent.Height isn't what I expect below
                            if (newY + Height > parent.Height) newY = (float)(parent.Height - Height);
                            if (newY < 0) newY = 0;
                        }

                        SetY(newY);
                    }
                }

                break;

Here's the full source of the DraggableViewRenderer:

using Android.Content;
using Android.Views;
using TGB.Xamarin.Forms.TestApp.Droid.Renderers.Views;
using TGB.Xamarin.Forms.Views;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using static TGB.Xamarin.Forms.Views.DraggableView;
using xam = global::Xamarin.Forms;

[assembly: ExportRenderer(typeof(DraggableView), typeof(DraggableViewRenderer))]
namespace TGB.Xamarin.Forms.TestApp.Droid.Renderers.Views
{
    public class DraggableViewRenderer : VisualElementRenderer<xam.View>
    {
        float originalX;
        float originalY;
        float dX;
        float dY;
        bool firstTime = true;
        bool touchedDown = false;

        public DraggableViewRenderer(Context context) : base(context) { }

        protected override void OnElementChanged(ElementChangedEventArgs<xam.View> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null)
            {
                LongClick -= HandleLongClick;
            }

            if (e.NewElement != null)
            {
                LongClick += HandleLongClick;

                var dragView = Element as DraggableView;

                dragView.RestorePositionCommand = new Command(() =>
                {
                    if (!firstTime)
                    {
                        SetX(originalX);
                        SetY(originalY);
                    }
                });
            }
        }

        private void HandleLongClick(object sender, LongClickEventArgs e)
        {
            var dragView = Element as DraggableView;

            if (firstTime)
            {
                originalX = GetX();
                originalY = GetY();
                firstTime = false;
            }

            dragView.DragStarted();

            touchedDown = true;
        }

        public override bool OnTouchEvent(MotionEvent e)
        {
            float x = e.RawX;
            float y = e.RawY;

            var dragView = Element as DraggableView;
            var parent = dragView.Parent as xam.View;

            switch (e.Action)
            {
                case MotionEventActions.Down:
                    if (dragView.DragMode == DragModes.Touch)
                    {
                        if (!touchedDown)
                        {
                            if (firstTime)
                            {
                                originalX = GetX();
                                originalY = GetY();
                                firstTime = false;
                            }

                            dragView.DragStarted();
                        }

                        touchedDown = true;
                    }

                    dX = x - this.GetX();
                    dY = y - this.GetY();

                    break;

                case MotionEventActions.Move:
                    if (touchedDown)
                    {
                        if (dragView.DragDirection == DragDirectionTypes.All || dragView.DragDirection == DragDirectionTypes.Horizontal)
                        {
                            var newX = x - dX;

                            if (parent != null)
                            {
                                if (newX + Width > parent.Width) newX = (float)(parent.Width - Width);
                                if (newX < 0) newX = 0;
                            }

                            SetX(newX);
                        }

                        if (dragView.DragDirection == DragDirectionTypes.All || dragView.DragDirection == DragDirectionTypes.Vertical)
                        {
                            var newY = y - dY;

                            if (parent != null)
                            {
                                if (newY + Height > parent.Height) newY = (float)(parent.Height - Height);
                                if (newY < 0) newY = 0;
                            }

                            SetY(newY);
                        }
                    }

                    break;

                case MotionEventActions.Up:
                    touchedDown = false;

                    DraggableViewDragEndedEventArgs args = new DraggableViewDragEndedEventArgs
                    {
                        X = GetX(),
                        Y = GetY()
                    };

                    dragView.DragEnded(args);

                    break;

                case MotionEventActions.Cancel:
                    touchedDown = false;

                    break;
            }
            return base.OnTouchEvent(e);
        }

        public override bool OnInterceptTouchEvent(MotionEvent e)
        {
            BringToFront();
            return true;
        }
    }
}
Paul Sinnema
  • 2,534
  • 2
  • 20
  • 33

2 Answers2

1

parent.width is a width of a container in pixels. It can be less than display width. Also a container may be bigger than display if it overlaps its size (for instance, "android:width="1000dp"). You should add a layout here.

To get a display width see How to get screen dimensions as pixels in Android.

CoolMind
  • 26,736
  • 15
  • 188
  • 224
  • Thanks for the quick response. I suspected as much but the examples in the link don't really help me. I'm using Xamarin but all examples somehow use stuff I can't find in my renderer (like WindowManager). – Paul Sinnema May 21 '20 at 14:41
  • Well, you know a size of the device in pixels. Could you tell, what it equals to and how does it differ from `Width` (or parent.Width)? – CoolMind May 21 '20 at 14:56
1

Ok the problem was Density as I suspected. Xamarin.Essentials has the solution. Do this in you code:

var density = global::Xamarin.Essentials.DeviceDisplay.MainDisplayInfo.Density; // 2.625 in my case

Multiply the parent.Width and parent.Height with density and you get the width in pixels

var width  = parent.Width * density;
var height = parent.Height * density;
Paul Sinnema
  • 2,534
  • 2
  • 20
  • 33