Xamarin 表单:如何为 StackLayout 添加 SwipeGestureRecognizer?
Xamarin forms: How to add SwipeGestureRecognizer for StackLayout?
我的主页上有 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 没有任何反应,并且代码执行没有进入事件函数。我在这里缺少什么?
原因 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>
我喜欢并使用了 Lucas Zhang 对这个问题的回答 但是,可以更改 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 中的 Up/Down 方向,但其他实现也是如此。
我的主页上有 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 没有任何反应,并且代码执行没有进入事件函数。我在这里缺少什么?
原因 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>
我喜欢并使用了 Lucas Zhang 对这个问题的回答
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 中的 Up/Down 方向,但其他实现也是如此。