Gestures inside a scrollview are not working

Brian Erickson 346 Reputation points
2022-01-18T02:51:02.747+00:00

I'd like a scrollview with swipe left, swipe right, and pinch/zoom gestures enabled. In a frame, all the gestures work fine. Adding a scrollview to the mix breaks everything. The swipe events are not fired. Neither is the pinchupdated event. I've tried:

  1. Stacklayout inside the Scrollview
  2. Stacklayout inside a frame inside a scrollview
  3. Stacklayout inside a scrollview inside a frame

For #2 and #3 it doesn't matter if the gesture recognizers are on the frame or scrollview...I can't get it to work.

Any help would be appreciated.

Here's the xaml...

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ESoftware.Rosary.MainPageDetail"
             Title="Detail">
      <ScrollView>
         <ScrollView.GestureRecognizers>
         <SwipeGestureRecognizer x:Name="SwipeLeft" Direction="Left" Swiped="SwipeLeft_Swiped"></SwipeGestureRecognizer>
         <SwipeGestureRecognizer x:Name="SwipeRight" Direction="Right" Swiped="SwipeRight_Swiped"></SwipeGestureRecognizer>
         <PinchGestureRecognizer x:Name="Pinch" PinchUpdated="Pinch_PinchUpdated"></PinchGestureRecognizer>
      </ScrollView.GestureRecognizers>
      <Frame>
         <StackLayout Orientation="Vertical">
         <StackLayout x:Name="StkPinchInfo" IsVisible="false" Orientation="Vertical">
            <StackLayout Orientation="Horizontal">
               <Label Text="Scale:"></Label>
               <Label x:Name="PinchScale" Text=""></Label>
            </StackLayout>
            <StackLayout Orientation="Horizontal">
               <Label Text="Font Size:"></Label>
               <Label x:Name="PinchFontSize" Text=""></Label>
            </StackLayout>
         </StackLayout>
         <StackLayout x:Name="stkPrevNext" Orientation="Horizontal" IsVisible="false">
            <Button x:Name="BtnPrev" Text="Prev" Clicked="BtnPrev_Clicked"></Button>
            <Button x:Name="BtnNext" Text="Next" Clicked="BtnNext_Clicked"></Button>
         </StackLayout>
         <Label x:Name="MysteryTitle"    HorizontalOptions="Start" FontSize="Medium" Margin="20,20,0,0"></Label>
         <Label x:Name="MysteryText"     HorizontalOptions="Start" FontSize="Medium" Margin="20,20,0,0"></Label>
         <Label x:Name="PrayerTitle"     HorizontalOptions="Start" FontSize="Medium" Margin="20,20,0,0"/>
         <Label x:Name="PrayerTextBold"  HorizontalOptions="Start" FontSize="Medium" Margin="40,5,0,0" FontAttributes="Bold"/>
         <Label x:Name="PrayerText"      HorizontalOptions="Start" FontSize="Medium" Margin="40,15,0,0"/>

         <Label x:Name="Prayer2Title"    HorizontalOptions="Start" FontSize="Medium" Margin="20,20,0,0"/>
         <Label x:Name="PrayerText2Bold" HorizontalOptions="Start" FontSize="Medium" Margin="40,5,0,0" FontAttributes="Bold"/>
         <Label x:Name="PrayerText2"     HorizontalOptions="Start" FontSize="Medium" Margin="40,15,0,0"/>

         <ProgressBar x:Name="pbDecade"  HeightRequest="10" Margin="40,10,40,0"></ProgressBar>
      </StackLayout>
      </Frame>
   </ScrollView>
</ContentPage>
Developer technologies | .NET | Xamarin
0 comments No comments
{count} votes

Accepted answer
  1. Wenyan Zhang (Shanghai Wicresoft Co,.Ltd.) 36,436 Reputation points Microsoft External Staff
    2022-01-18T09:31:38.453+00:00

    Hello,

    Welcome to our Microsoft Q&A platform!

    I set set a height for StackLayout that goes beyond the screen, the Gesture works on iOS but not on Android. If you put the stacklayout inside a ScrollView, the swipe action will conflict with the scroll action. There is a workaround, you have to use Custom Renderers, refer to the following code:

    Customize a scrollview in your Xamarin project

    using System;  
    using Xamarin.Forms;  
      
    namespace ScrollViewDemo  
    {  
        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);  
        }  
    }  
    

    Custom Renderer in Android Project

    [assembly: ExportRenderer(typeof(GestureScrollView), typeof(GestureScrollViewRenderer))]  
    namespace ScrollViewDemo.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);  
            }  
        }  
    }  
    

    Use this scrollview in XAML

    <?xml version="1.0" encoding="utf-8" ?>  
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"  
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"  
                 xmlns:local="clr-namespace:ScrollViewDemo"  
                 x:Class="ScrollViewDemo.MainPage">  
        <local:GestureScrollView x:Name="MyGestureScrollView">  
            <ScrollView.GestureRecognizers>  
                <SwipeGestureRecognizer x:Name="SwipeLeft" Direction="Left" Swiped="SwipeLeft_Swiped"></SwipeGestureRecognizer>  
                <SwipeGestureRecognizer x:Name="SwipeRight" Direction="Right" Swiped ="SwipeRight_Swiped"></SwipeGestureRecognizer>  
                <PinchGestureRecognizer x:Name="Pinch"  PinchUpdated="Pinch_PinchUpdated"></PinchGestureRecognizer>  
            </ScrollView.GestureRecognizers>  
      
            <Frame >  
                <StackLayout BackgroundColor="Yellow"  
                             HeightRequest="1000"  
                             >  
                </StackLayout>  
            </Frame>  
              
        </local:GestureScrollView>  
    </ContentPage>  
    

    CodeBehind

    namespace ScrollViewDemo  
    {  
        public partial class MainPage : ContentPage  
        {  
            public MainPage()  
            {  
                InitializeComponent();  
                MyGestureScrollView.SwipeLeft += (s, e) =>  
        DisplayAlert("Gesture Info", "Swipe Left Detected", "OK");  
                MyGestureScrollView.SwipeRight += (s, e) =>  
        DisplayAlert("Gesture Info", "Swipe Right Detected", "OK");  
            }  
            // It works on iOS   
            private void SwipeLeft_Swiped(object sender, SwipedEventArgs e)  
            {  
                Console.WriteLine("{SwipeLeft_Swiped---------------}");  
            }  
      
            private void SwipeRight_Swiped(object sender, SwipedEventArgs e)  
            {  
                Console.WriteLine("{SwipeRight_Swiped---------------}");  
            }  
      
            private void Pinch_PinchUpdated(object sender, PinchGestureUpdatedEventArgs e)  
            {  
                Console.WriteLine("{Pinch_PinchUpdated---------------}");  
            }  
        }  
    }  
    

    About the pinch and zoom action, you could refer to
    https://stackoverflow.com/questions/37289517/xamarin-forms-label-in-a-scroll-view-pinch-and-zoom-android
    https://social.msdn.microsoft.com/Forums/en-US/2474e9eb-ee00-45cf-86ff-af9554bc2011/is-it-possible-to-achieve-pinch-zoom-on-scrollview-control?forum=xamarincrossplatform

    Best Regards,
    Wenyan Zhang


    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. Brian Erickson 346 Reputation points
    2022-01-19T14:49:05.25+00:00

    To avoid any custom Android code you can do this.

    In the constructor:

         //Needed on Android so the frame gets gesture events.  But only Android (not sure about IoS)
         if (Device.RuntimePlatform == Device.Android)
            ScrollControl.InputTransparent = true;
    

    To set the height of the scrollview to something reasonable.

      protected override void OnSizeAllocated(double width, double height)
      {
         ScrollControl.HeightRequest = height;
         base.OnSizeAllocated(width, height);
      }
    

    To do the scrolling:

      private void SwipeDown_Swiped(object sender, SwipedEventArgs e)
      {
         double ScrollToPos;
         double ScrollAmount = ScrollControl.Height * 0.9;
    
         //Only take an action if we're not at the top.
         if( ScrollControl.ScrollY > 0 )
         {
            if (ScrollControl.ScrollY - ScrollAmount >= 0)
               ScrollToPos = ScrollControl.ScrollY - ScrollAmount;
            else
               ScrollToPos = 0.0D;
    
            ScrollControl.ScrollToAsync( 0.0D, ScrollToPos, true );
         }
      }
    
      private void SwipeUp_Swiped(object sender, SwipedEventArgs e)
      {
         double ScrollToPos;
         double ScrollAmount = ScrollControl.Height * 0.9;
    
         //Only take an action if we're not at the bottom.
         if (ScrollControl.ScrollY + ScrollControl.Height < MainFrame.Height)
         {
            if (ScrollControl.ScrollY + ScrollAmount < MainFrame.Height)
               ScrollToPos = ScrollControl.ScrollY + ScrollAmount;
            else
               ScrollToPos = MainFrame.Height - ScrollControl.Height;
    
            ScrollControl.ScrollToAsync(0.0D, ScrollToPos, true);
         }
      }
    
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.