【问题标题】:Xamarin forms: How to add SwipeGestureRecognizer for StackLayout?Xamarin 表单:如何为 StackLayout 添加 SwipeGestureRecognizer?
【发布时间】:2019-12-05 06:05:05
【问题描述】:

我的主页上有 15 个选项。最初,我将在 UI 中显示 9 个选项。要查看剩余的 6 个图标,用户可以向右滑动,然后再向左滑动以查看上一个。我尝试实现如下所示的滑动功能,但它不起作用。

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;
}

当尝试左右滑动时,UI 中没有发生任何事情,并且代码执行不会进入事件函数。我在这里缺少什么?

【问题讨论】:

    标签: xamarin.forms swipe-gesture stacklayout


    【解决方案1】:

    原因1:

    如果您将堆栈布局放在 ScrollView 中,滑动操作将与滚动操作发生冲突。

    解决方案:

    从 Root StackLayout 中移除 ScrollView,然后滑动就可以了。

    原因2:需要在StackLayout中添加子控件(如Image或Label),否则永远不会调用滑动动作。

    解决方案:如果你想让StackLayout的内容默认不显示,可以查看以下代码。

    在后面的代码中

    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);
        }
    }
    

    在 Android 项目中

    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);
            }
        }
    }
    

    在 Xaml 中

    将StackLayout放到ScrollView中

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

    【讨论】:

    • 现在我注意到刷卡最初不起作用。尝试几次后,它开始工作。我删除了根堆栈布局,但仍然有这个问题。有没有cmets?
    【解决方案2】:

    我喜欢并使用 Lucas Zhang 对这个问题的回答 answer-59190549 但是,可以更改 GestureScrollView 以使用 iOS 将使用的相同 GestureRecognizer,如下所示:

    public class GestureScrollView : ScrollView
    {
        private bool isInitialized = false;
        private List<SwipeGestureRecognizer> LeftSwipeRecognizers { get; } = new();
        private List<SwipeGestureRecognizer> RightSwipeRecognizers { get; } = new();
    
        public GestureScrollView() : base()
        {
        }
    
        protected override void LayoutChildren(double x, double y, double width, double height)
        {
            base.LayoutChildren(x, y, width, height);
    
            //Not sure if this is the best place, but the ctor wasn't getting called.
            if (!isInitialized)
            {
                isInitialized = true;
                foreach (SwipeGestureRecognizer swipeGestureRecognizer in GestureRecognizers.Where(x => x is SwipeGestureRecognizer))
                {
                    if (swipeGestureRecognizer.Direction.HasFlag(SwipeDirection.Left))
                    {
                        LeftSwipeRecognizers.Add(swipeGestureRecognizer);
                    }
    
                    if (swipeGestureRecognizer.Direction.HasFlag(SwipeDirection.Right))
                    {
                        RightSwipeRecognizers.Add(swipeGestureRecognizer);
                    }
                }
            }
        }
    
        private void ExecuteGestureCommands(List<SwipeGestureRecognizer> swipeRecognizers, SwipedEventArgs e)
        {
            foreach (var gesture in swipeRecognizers)
            {
                gesture.SendSwiped(this, e.Direction);
            }
        }
    
        public void OnSwipeLeft(object sender, SwipedEventArgs e)
        {
            ExecuteGestureCommands(LeftSwipeRecognizers, e);
        }
    
        public void OnSwipeRight(object sender, SwipedEventArgs e)
        {
            ExecuteGestureCommands(RightSwipeRecognizers, e);
        }
    }
    

    渲染器需要与这些类似的更改(基本上将所有“EventArgs”引用更改为“SwipedEventArgs”并在事件调用中传入一个新的 SwipedEventArgs):

    ... 
    void HandleOnSwipeLeft(object sender, SwipedEventArgs e) => ((GestureScrollView)Element).OnSwipeLeft(sender, e);
    void HandleOnSwipeRight(object sender, SwipedEventArgs e) => ((GestureScrollView)Element).OnSwipeRight(sender, e);
    ...
    public event EventHandler<SwipedEventArgs> OnSwipeLeft;
    public event EventHandler<SwipedEventArgs> OnSwipeRight;
    ...
    if (diffX > 0)
    {
        OnSwipeRight?.Invoke(this, new SwipedEventArgs(null, SwipeDirection.Right));
    }
    else
    {
        OnSwipeLeft?.Invoke(this, new SwipedEventArgs(null, SwipeDirection.Left));
    }
    ...
    

    它并不完美,因为它忽略了 Android 中的向上/向下方向,但其他实现也是如此。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-01-19
      • 2017-12-30
      • 2019-04-02
      • 2016-09-07
      • 1970-01-01
      • 2017-05-04
      • 2017-06-28
      • 1970-01-01
      相关资源
      最近更新 更多