Swipe to close a stacklayout with other input controls

Tony Pitman 41 Reputation points
2022-11-23T22:52:47.517+00:00

I have a xamarin forms app for ios and android.

I have a hamburger menu in the upper left. When the user taps it I slide out a view that has several controls on it. This includes grids, entry(s), labels, buttons, etc. The user needs to be able to interact with all of those controls.

My customer just asked if the user could just swipe to the left on this panel to close it.

I can't seem to figure out how to overlay a control on top to put the swipe gesture recognizer on and still allow interacting with all the controls.

I did try putting a gesture recognizer on every single control in the view and have them all point to the same Command to close the window.

This only works sometimes. Most of the time you swipe and swipe and either get into the control (like with the Entry field) or nothing happens.

Once in a while it does call the Command and close the view.

Is there a way to do this?

Xamarin
Xamarin
A Microsoft open-source app platform for building Android and iOS apps with .NET and C#.
5,288 questions
0 comments No comments
{count} votes

Accepted answer
  1. Yonglun Liu (Shanghai Wicresoft Co,.Ltd.) 34,841 Reputation points Microsoft Vendor
    2022-11-24T05:45:19.457+00:00

    Hello,

    This should be a conflict due to the Android touch event distribution mechanism.

    The onInterceptTouchEvent() method is called whenever a touch event is detected on the surface of a ViewGroup, including on the surface of its children. If onInterceptTouchEvent() returns true, the MotionEvent is intercepted, meaning it is not passed on to the child, but rather to the onTouchEvent() method of the parent.

    Therefore, you need to use Custom Renderer to override the onInterceptTouchEvent() to manage touch events.

    The following documentations could be helpful for you.

    On iOS, after my testing, the results show that the page swipe gesture doesn't conflict with the input controls.

    Best Regards,

    Alec Liu.


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


1 additional answer

Sort by: Most helpful
  1. Tony Pitman 41 Reputation points
    2022-11-28T14:25:07.64+00:00

    I am leaving YonglunLiu's response as the answer because he gave the tip that allowed me to create this solution.

    The page I wanted to be able to swipe left on was contained inside of a Grid, so I created a derived class called SwipeGrid. It doesn't contain any logic inside of it. It is just the subclass:

    using Xamarin.Forms;  
      
    namespace MyApp.Controls  
    {  
      public class SwipeGrid: Grid  
      {  
      }  
    }  
    

    iOS doesn't need any help with swiping. It works great all by itself. For this reason in iOS you just make a SwipeGridRenderer that doesn't change anything. I do not know if you can just not have this class. I think it is required.

    using MyApp.Controls;  
    using MyApp.iOS;  
    using UIKit;  
    using Xamarin.Forms;  
    using Xamarin.Forms.Platform.iOS;  
      
    [assembly: ExportRenderer(typeof(SwipeGrid), typeof(SwipeGridRenderer))]  
    namespace MyApp.iOS  
    {  
    	public class SwipeGridRenderer : ViewRenderer<Grid, UIView>  
        {  
    	}  
    }  
      
    

    Android is the real issue and so I had to create a SwipeGridRenderer with the following:

    using System;  
    using Android.Content;  
    using Android.Widget;  
    using MyApp.Droid;  
    using Xamarin.Forms;  
    using Xamarin.Forms.Platform.Android;  
    using MyApp.Controls;  
    using Android.Views;  
      
    [assembly: ExportRenderer(typeof(SwipeGrid), typeof(SwipeGridRenderer))]  
    namespace MyApp.Droid  
    {  
        public class SwipeGridRenderer : VisualElementRenderer<Grid>  
        {  
            private int ScaledTouchSlop;  
            private MotionEvent DownEvent;  
            private bool Swiping;  
      
            public SwipeGridRenderer(Context context) : base(context)  
            {  
                ScaledTouchSlop = ViewConfiguration.Get(context).ScaledTouchSlop;  
                Swiping = false;  
            }  
      
            public override bool OnInterceptTouchEvent(MotionEvent ev)  
            {  
                switch (ev.Action)  
                {  
                    case MotionEventActions.Cancel:  
                    case MotionEventActions.Up:  
                        Swiping = false;  
                        break;  
      
                    case MotionEventActions.Down:  
                        Swiping = false;  
                        DownEvent = MotionEvent.Obtain(ev);  
                        break;  
      
                    case MotionEventActions.Move:  
                        float diffX = DownEvent.GetX() - ev.GetX();  
                        Swiping = diffX > ScaledTouchSlop;  
                        if (Swiping)  
                            OnTouchEvent(DownEvent);  
                        break;  
      
                    default:  
                        break;  
                }  
      
                return Swiping ? true : base.OnInterceptTouchEvent(ev);  
            }  
      
            public override bool OnTouchEvent(MotionEvent e)  
            {  
                if (e.Action == MotionEventActions.Up || e.Action == MotionEventActions.Cancel)  
                    Swiping = false;  
      
                return base.OnTouchEvent(e);  
            }  
        }  
    }  
      
    

    If you understand what is going on you will detect that this will only detect swiping to the left. It also doesn't constrain that swiping to being very horizontal. I had a very specific thing I was trying to do and this is the minimum needed to get it done.

    You could modify this to do a lot more.

    You may also notice that I capture the down event and then send it inside of the move event. This is because at the moment you get the down touch we have not yet detected that the user is swiping because we need movement for that.

    If you just start capturing the moves once you detect the swipe, the gesture recognizer doesn't recognize the swipe because it never got a down at the start of the gesture.

    This requires that a down event be sent to the Grid before sending moves from that point to the Up event.

    You may also notice that we don't really process the event in the OnTouchEvent handler other than detecting Up or Cancel to cancel capturing touch events. This is because I am relying on the gesture recognizer to see the swipe and process it like normal.

    0 comments No comments