xamarin.formsswipe-gesturestacklayout

Xamarin forms: How to add SwipeGestureRecognizer for StackLayout?


I have 15 options on my home page. Initially, I will show 9 options in UI. To view the remaining 6 icons, the user slides to the right and back to the left to see previous. I try to implement the swiping feature like below, but it is not working.

XAML

<StackLayout x:Name="firstLlayout">
    <Grid>
        //3 icons in horizontal
    </Grid>

    <Grid>
        //3 icons in horizontal
    </Grid>

    <Grid>
        //3 icons in horizontal
    </Grid>
    <StackLayout.GestureRecognizers>
        <SwipeGestureRecognizer Direction="Right" Swiped="RightSwipe"/>
    </StackLayout.GestureRecognizers>
</StackLayout>

<StackLayout IsVisible="False" x:Name="secondLayout">
     <Grid>
        //3 icons in horizontal
    </Grid>

    <Grid>
        //3 icons in horizontal
    </Grid>
    <StackLayout.GestureRecognizers>
        <SwipeGestureRecognizer Direction="Left" Swiped="LeftSwipe"/>
    </StackLayout.GestureRecognizers>
</StackLayout>

XAML.CS

public void RightSwipe(object sender, EventArgs e)
{
    firstLlayout.IsVisible = false;
    secondLayout.IsVisible = true;
}

public void LeftSwipe(object sender, EventArgs e)
{
    secondLayout.IsVisible = false;
    firstLlayout.IsVisible = true;
}

When try left and right swipe nothing is happening in UI, and code execution not coming to event functions. What I am missing here?


Solution

  • Cause1:

    Swipe action will conflict with scroll action if you put the stacklayout in a ScrollView .

    Solution:

    Remove the ScrollView from Root StackLayout, then the swiping will work.

    Cause2: It is necessary to add a child control(like Image or Label)to StackLayout , otherwise the swipe action will never been called .

    Solution: If you do want to let the content of StackLayout shows nothing in default, you can check the following code .

    in code behind

    using System;
    using Xamarin.Forms;
    
    namespace xxx
    {
        public class GestureScrollView : ScrollView
        {
            public event EventHandler SwipeLeft;
            public event EventHandler SwipeRight;
    
            public void OnSwipeLeft() =>
                SwipeLeft?.Invoke(this, null);
    
            public void OnSwipeRight() =>
                SwipeRight?.Invoke(this, null);
        }
    }
    

    in Android Project

    using System;
    using Android.Content;
    using Android.OS;
    using Android.Runtime;
    using Android.Views;
    using Android.Widget;
    using xxx;
    using xxx.Droid;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.Android;
    
    [assembly: ExportRenderer(typeof(GestureScrollView), typeof(GestureScrollViewRenderer))]
    namespace xxx.Droid
    {
        public class GestureScrollViewRenderer : ScrollViewRenderer
        {
            readonly CustomGestureListener _listener;
            readonly GestureDetector _detector;
    
            public GestureScrollViewRenderer(Context context) : base(context)
            {
                _listener = new CustomGestureListener();
                _detector = new GestureDetector(context, _listener);
            }
    
            public override bool DispatchTouchEvent(MotionEvent e)
            {
                if (_detector != null)
                {
                    _detector.OnTouchEvent(e);
                    base.DispatchTouchEvent(e);
                    return true;
                }
    
                return base.DispatchTouchEvent(e);
            }
    
            public override bool OnTouchEvent(MotionEvent ev)
            {
                base.OnTouchEvent(ev);
    
                if (_detector != null)
                    return _detector.OnTouchEvent(ev);
    
                return false;
            }
    
            protected override void OnElementChanged(VisualElementChangedEventArgs e)
            {
                base.OnElementChanged(e);
    
                if (e.NewElement == null)
                {
                    _listener.OnSwipeLeft -= HandleOnSwipeLeft;
                    _listener.OnSwipeRight -= HandleOnSwipeRight;
                }
    
                if (e.OldElement == null)
                {
                    _listener.OnSwipeLeft += HandleOnSwipeLeft;
                    _listener.OnSwipeRight += HandleOnSwipeRight;
                }
            }
    
            void HandleOnSwipeLeft(object sender, EventArgs e) =>
                ((GestureScrollView)Element).OnSwipeLeft();
    
            void HandleOnSwipeRight(object sender, EventArgs e) =>
                ((GestureScrollView)Element).OnSwipeRight();
        }
    
        public class CustomGestureListener : GestureDetector.SimpleOnGestureListener
        {
            static readonly int SWIPE_THRESHOLD = 100;
            static readonly int SWIPE_VELOCITY_THRESHOLD = 100;
    
            MotionEvent mLastOnDownEvent;
    
            public event EventHandler OnSwipeLeft;
            public event EventHandler OnSwipeRight;
    
            public override bool OnDown(MotionEvent e)
            {
                mLastOnDownEvent = e;
    
                return true;
            }
    
            public override bool OnFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
            {
                if (e1 == null)
                    e1 = mLastOnDownEvent;
    
                float diffY = e2.GetY() - e1.GetY();
                float diffX = e2.GetX() - e1.GetX();
    
                if (Math.Abs(diffX) > Math.Abs(diffY))
                {
                    if (Math.Abs(diffX) > SWIPE_THRESHOLD && Math.Abs(velocityX) > SWIPE_VELOCITY_THRESHOLD)
                    {
                        if (diffX > 0)
                            OnSwipeRight?.Invoke(this, null);
                        else
                            OnSwipeLeft?.Invoke(this, null);
                    }
                }
    
                return base.OnFling(e1, e2, velocityX, velocityY);
            }
        }
    }
    

    And in Xaml

    Put the StackLayout in the ScrollView

    <local:GestureScrollView SwipeRight="RightSwipe">
         <StackLayout x:Name="firstLlayout" >
    
            //...        
         </StackLayout>
    
              
    </local:GestureScrollView>