question

TonyPitman-1668 avatar image
0 Votes"
TonyPitman-1668 asked TonyPitman-1668 answered

Swipe to close a stacklayout with other input controls

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?

dotnet-xamarin
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

YonglunLiu-MSFT avatar image
0 Votes"
YonglunLiu-MSFT answered YonglunLiu-MSFT edited

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.


· 4
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

You are correct that the swipe works great on iOS.

I can't find the correct class to create a custom renderer on that would allow me to override the onInterceptTouchEvent method.

I have my swipe gesture on the root StackLayout object in my xaml (which works on iOS). When I create a new class called SwipeStackLayout and derive it from StackLayout, there isn't an override for onInterceptTouchEvent or anything related that I can find.

Would you please give me more details about exactly where I am to override that method in a class derived from something that relates to the documentation you gave links to?

Update: I decided to try deriving my class from ViewGroup and it does have the onInterceptTouchEvent method in it. The problem with using it is that it only exists in Android and not iOS, so I can't make the generic class that will need to be used in the shared project to be able to have it in my XAML.

How do I deal with this? The documentation you shared shows examples that always have a shared class and then the details are done in the derived class.

Please clarify

0 Votes 0 ·

Nevermind. I figured out what needed to be done. I created the basic renderer class that is shared and didn't override anything there.

Then in the Android specific version I found the override that is needed. Sorry for my ignorance.

I have now the override and am trying to figure out how I want to detect the left swipe and process the value.

0 Votes 0 ·

I was able to get this to work. Here is what I ended up doing on Android:

If anyone would like my solution, please PM me. I tried to paste it here and didn't have enough characters in the comment.

I didn't want to post my own solution because it was Alec that really sent me in the right direction.

0 Votes 0 ·
Show more comments
TonyPitman-1668 avatar image
0 Votes"
TonyPitman-1668 answered

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.

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.