Horizontal ScrollView in CarouselPage

FotalFotal Member ✭✭✭
edited July 2018 in Xamarin.Forms

Hello!
I have a ScrollView (_HorizontalMoveEnable = true; _) in the ContentPage in CarouselPage items.

I created my own ZoomScrollViewRenderer, allowing me to zoom in. I also worked on improving the recognition of gestures to go to another page of CarouselPage.

When I'm at the beginning of the ScrollView (X position = 0) and I swipe to the left, it should go to the left page. When I'm at the end of the ScrollView (X position = ContentSize.Width) and I swipe to the right, it should go to the right page.

The problem is that it only works after two swipes and I do not know how to improve it.

I hope I managed to explain my problem.

Please see my ZoomScrollViewRender and help improve the method DispatchTouchEvent

public class ZoomScrollViewRenderer : ScrollViewRenderer
{
    private Context _context;

    private ScaleGestureDetector _scaleDetector;
    private bool _isScaleProcess = false;

    private float X1 { get; set; }
    private float X2 { get; set; }


    public ZoomScrollViewRenderer(Context context) : base(context)
    {
        this._context = context;
    }


    protected override void OnElementChanged(VisualElementChangedEventArgs e)
    {
        base.OnElementChanged(e);

        if (e.NewElement != null)
        {
            _scaleDetector = new ScaleGestureDetector(Context, new ClearScaleListener(
                scale =>
                {
                    var scrollView = (ZoomScrollView)Element;

                    var xRatio = scale.FocusX / Width;
                    var yRatio = scale.FocusY / Height;

                    scrollView.AnchorX = xRatio;
                    scrollView.AnchorY = yRatio;
                },
                scale =>
                {
                    _isScaleProcess = true;
                    var scrollView = (ZoomScrollView)Element;

                    var zoom = new Pinch(scale.ScaleFactor, scale.FocusX, scale.FocusY);
                    scrollView.OnZoom(zoom);
                }));
        }
    }


    public override bool DispatchTouchEvent(MotionEvent e)
    {
        var scrollView = (ZoomScrollView)Element;

        // Scale if 2 fingers on screen
        if (e.PointerCount == 2)
            return _scaleDetector.OnTouchEvent(e);

        // Ignore horizontal move when content stretched to the width of the screen
        if (!scrollView.HorizontalMoveEnable)
            return base.DispatchTouchEvent(e);

        // Detect first X position 
        if (MotionEventActions.Down == e.Action)
        {
            X1 = e.RawX;
            return base.DispatchTouchEvent(e);
        }

        if (_isScaleProcess)
        {
            //HACK:
            //Prevent letting any touch events from moving the scroll view until all fingers are up from zooming...This prevents the jumping and skipping around after user zooms.
            if (MotionEventActions.Up == e.Action)
                _isScaleProcess = false;
            return false;
        }


        // Detect second X position
        if (MotionEventActions.Up == e.Action
            || MotionEventActions.Cancel == e.Action)
        {
            X2 = e.RawX;
        }

        // Left or right swipe
        if (X2 - X1 > 0 && scrollView.IsStartScroll
            || X1 - X2 >= 0 && scrollView.IsEndScroll)
        {

            return base.OnTouchEvent(e);
        }

        return base.DispatchTouchEvent(e);
    }
}

ZoomScrollView in Portable project:

public class ZoomScrollView : ScrollView
    {
        public event EventHandler<Pinch> Zoom;


        public bool HorizontalMoveEnable { get; set; } = false;

        public ScrollOrientation ScrollOrientation
        {
            get => Orientation;
            set
            {
                if (value.Equals(Orientation))
                    return;
                Orientation = value;
            }
        }

        public bool IsStartScroll => Math.Abs(0 - ScrollX) < 10;

        public bool IsEndScroll => Math.Abs(ContentSize.Width - Width - ScrollX) < 10;



        public virtual void OnZoom(Pinch pinch)
        {
            Zoom?.Invoke(this, pinch);
        }
    }

Answers

  • BillyLiuBillyLiu Member, Xamarin Team Xamurai

    Hi @Fotal ,

    It seems your code is incomplete. What's the ClearScaleListener ,scale and Pinch?
    Could you provide all related codes so that I can reproduce the issue and help you to solve it?

  • FotalFotal Member ✭✭✭
    edited July 2018

    Hi, @BillyLiu

    I worked a little more on my implementation and I kind of got it. Quick Swaps work fine, but if you do a long and slow, then most of the time it is ignored.

    To test it, create a CarouselPage, place the ZoomScrollView on each page and assign large content

    ZoomScrollView.cs PCL

    public class ZoomScrollView : ScrollView
        {
            public event EventHandler<Pinch> Zoom;
    
    
            public bool HorizontalMoveEnable { get; set; } = false;
    
            public ScrollOrientation ScrollOrientation
            {
                get => Orientation;
                set
                {
                    if (value.Equals(Orientation))
                        return;
                    Orientation = value;
                }
            }
    
            public bool IsStartScroll => Math.Abs(0 - ScrollX) < 10;
    
            public bool IsEndScroll => Math.Abs(ContentSize.Width - Width - ScrollX) < 10;
    
    
    
            public virtual void OnZoom(Pinch pinch)
            {
                Zoom?.Invoke(this, pinch);
            }
        }
    

    ZoomScrollViewRenderer.cs Android

    [assembly: ExportRenderer(typeof(ZoomScrollView), typeof(ZoomScrollViewRenderer))]
    namespace [namespace].Droid.Controls
    {
        public class ZoomScrollViewRenderer : ScrollViewRenderer
        {
            private Context _context;
    
            private GestureDetector _swipeDetector;
            private ScaleGestureDetector _scaleDetector;
            private bool _isScaleProcess = false;
    
            private float _x1;
            private float _x2;
    
            private bool _rightSwipe;
            private bool _leftSwipe;
    
            private ZoomScrollView _scrollView;
    
    
            public ZoomScrollViewRenderer(Context context) : base(context)
            {
                this._context = context;
            }
    
    
            protected override void OnElementChanged(VisualElementChangedEventArgs e)
            {
                base.OnElementChanged(e);
    
                if (e.NewElement != null)
                {
                    _swipeDetector = new GestureDetector(Context, new SwipeGestureListener(
                        swipeRight: () => _rightSwipe = true,
                        swipeLeft: () => _leftSwipe = true));
    
                    _scaleDetector = new ScaleGestureDetector(Context, new ClearScaleListener(
                        scale =>
                        {
                            var scrollView = (ZoomScrollView)Element;
    
                            var xRatio = scale.FocusX / Width;
                            var yRatio = scale.FocusY / Height;
    
                            scrollView.AnchorX = xRatio;
                            scrollView.AnchorY = yRatio;
                        },
                        scale =>
                        {
                            _isScaleProcess = true;
                            var scrollView = (ZoomScrollView)Element;
    
                            var zoom = new Pinch(scale.ScaleFactor, scale.FocusX, scale.FocusY);
                            scrollView.OnZoom(zoom);
                        }));
                }
    
                _scrollView = (ZoomScrollView)Element;
            }
    
    
            public override bool DispatchTouchEvent(MotionEvent e)
            {
                // Scale if 2 fingers on screen
                if (e.PointerCount == 2)
                    return _scaleDetector.OnTouchEvent(e);
    
                // Ignore horizontal move when content stretched to the width of the screens
                if (!_scrollView.HorizontalMoveEnable)
                    return base.DispatchTouchEvent(e);
    
    
                // Left or right swipe
                if (_rightSwipe && _scrollView.IsStartScroll
                    || _leftSwipe && _scrollView.IsEndScroll)
                {
                    _rightSwipe = _leftSwipe = false;
                    return base.OnTouchEvent(e);
                }
    
                if (_isScaleProcess)
                {
                    //HACK:
                    //Prevent letting any touch events from moving the scroll view until all fingers are up from zooming...This prevents the jumping and skipping around after user zooms.
                    if (MotionEventActions.Up == e.Action
                        || MotionEventActions.Cancel == e.Action)
                    {
                        _isScaleProcess = false;
                    }
                    return false;
                }
    
    
                _swipeDetector.OnTouchEvent(e);
                return base.DispatchTouchEvent(e);
            } 
        }
    
        public class ClearScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener
        {
            private readonly Action<ScaleGestureDetector> _onScale;
            private readonly Action<ScaleGestureDetector> _onScaleBegin;
            private bool _skip = false;
    
            public ClearScaleListener(Action<ScaleGestureDetector> onScaleBegin, Action<ScaleGestureDetector> onScale)
            {
                _onScale = onScale;
                _onScaleBegin = onScaleBegin;
            }
    
            public override bool OnScale(ScaleGestureDetector detector)
            {
                if (_skip)
                {
                    _skip = false;
                    return true;
                }
                _onScale?.Invoke(detector);
                return true;
            }
    
            public override bool OnScaleBegin(ScaleGestureDetector detector)
            {
                _skip = true;
                _onScaleBegin.Invoke(detector);
                return true;
            }
        }
    
    
        public class SwipeGestureListener : GestureDetector.SimpleOnGestureListener
        {
            private const int SwipeThreshold = 50;
            private const int SwipeVelocityThreshold = 50;
    
            private readonly Action _swipeRight;
            private readonly Action _swipeLeft;
    
    
            public SwipeGestureListener(Action swipeRight, Action swipeLeft)
            {
                _swipeRight = swipeRight;
                _swipeLeft = swipeLeft;
            }
    
    
            public override bool OnDown(MotionEvent e)
            {
                return true;
            }
    
            public override bool OnFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
            {
                bool result = false;
                try
                {
                    float diffX = e2.GetX() - e1.GetX();
    
                    if (Math.Abs(diffX) > SwipeThreshold 
                        && Math.Abs(velocityX) > SwipeVelocityThreshold)
                    {
                        if (diffX > 0)
                        {
                            _swipeRight();
                        }
                        else
                        {
                            _swipeLeft();
                        }
                        result = true;
                    }
                }
                catch (Exception exception)
                {
                    Console.WriteLine(exception);
                }
    
                return result;
            }
        }   
    }
    

    OnZoom handler: in xaml.cs

    private void ScrollView_OnZoom(object sender, Pinch pinch)
            {
                if (pinch.ScaleFactor.Equals(1))
                { }
                else
                {
                    var heightEnd =
                        Math.Max(_minLayoutHeight, 
                            Math.Min(LayoutHeight * pinch.ScaleFactor, _maxLayoutHeight));
    
                    var widthEnd =
                        Math.Max(_minLayoutWidth,
                            Math.Min(LayoutWidth * pinch.ScaleFactor, MaxLayoutWidth));
    
                    ZoomScrollView.HorizontalMoveEnable = Math.Abs(widthEnd - _minLayoutWidth) > 0.1;
                    ZoomScrollView.ScrollOrientation =
                        ZoomScrollView.HorizontalMoveEnable ? ScrollOrientation.Both : ScrollOrientation.Vertical;
    
                    LayoutWidth = widthEnd;
                    LayoutHeight = heightEnd;
                }    
            }
    

    I'm very specific in processing Zoom, you can also test it, "Layout Width" and "LayoutHeight" are the properties of my Grid in the ZoomScrollView, and I increase its width and height

  • hakcomhakcom KRMember ✭✭

    new Pinch(scale.ScaleFactor, scale.FocusX, scale.FocusY);

    ?

Sign In or Register to comment.