[MAUI 项目实战] 手势控制音乐播放器(二): 手势交互
来源: 博客园 2023-04-08 18:29:41
@
目录原理交互实现容器控件手势开始手势运行手势结束使用控件拖拽物创建pit集合项目地址原理定义一个拖拽物,和它拖拽的目标,拖拽物可以理解为一个平底锅(pan),拖拽目标是一个坑(pit),当拖拽物进入坑时,拖拽物就会被吸附在坑里。可以脑补一下下图:
你问我为什么是平底锅和坑,当然了在微软官方的写法里pan是平移的意思,而不是指代平底锅。只是通过同义词来方便理解坑就是正好是平底锅大小的炉灶。正好可以放入平底锅。
(资料图片仅供参考)
pan和pit组成平移手势的系统,在具体代码中包含了边缘检测判定和状态机维护。我们将一步步实现平移手势功能
pit很简单,是一个包含了名称属性的控件,这个名称属性是用来标识pit的。以便当pan入坑时我们知道入了哪个坑,IsEnable是一个绑定属性,它用来控制pit是否可用的。
在这个程序中,拖拽物是一个抽象的唱盘。它的拖拽目标是周围8个图标。
交互实现这里用Grid作为pit控件基类型,因为Grid可以包含子控件,我们可以在pit控件中添加子控件,比如一个图片,一个文字,这样就可以让pit控件更加丰富。
public class PitGrid : Grid{ public PitGrid() { IsEnable = true; } public static readonly BindableProperty IsEnableProperty = BindableProperty.Create("IsEnable", typeof(bool), typeof(CircleSlider), true, propertyChanged: (bindable, oldValue, newValue) => { var obj = (PitGrid)bindable; obj.Opacity = obj.IsEnable ? 1 : 0.8; }); public bool IsEnable { get { return (bool)GetValue(IsEnableProperty); } set { SetValue(IsEnableProperty, value); } } public string PitName { get; set; }}
使用WeakReferenceMessenger作为消息中心,用来传递pan和pit的交互信息。
定义一个平移事件PanAction,在pan和pit产生交汇时触发。其参数PanActionArgs描述了pan和pit的交互的关系和状态。
public class PanActionArgs{ public PanActionArgs(PanType type, PitGrid pit = null) { PanType = type; CurrentPit = pit; } public PanType PanType { get; set; } public PitGrid CurrentPit { get; set; }}
手势状态类型PanType定义如下:
In:pan进入pit时触发,Out:pan离开pit时触发,Over:释放pan时触发,·Start:pan开始拖拽时触发public enum PanType{ Out, In, Over, Start}
MAUI为我们开发者包装好了PanGestureRecognizer
即平移手势识别器。
平移手势更改时引发事件PanUpdated
事件,此事件附带的 PanUpdatedEventArgs对象中包含以下属性:
PanGestureRecognizer
提供了当手指在屏幕移动这一过程的描述我们需要一个容器控件来对拖拽物进行包装,以赋予拖拽物响应平移手势的能力。
创建平移手势容器控件:在Controls目录中新建PanContainer.xaml,代码如下:
为PanContainer添加PitLayout属性,用来存放pit的集合。打开PanContainer.xaml.cs,添加如下代码:
private IList _pitLayout;public IList PitLayout{ get { return _pitLayout; } set { _pitLayout = value; }}
CurrentView属性为当前拖拽物所在的pit控件。
private PitGrid _currentView;public PitGrid CurrentView{ get { return _currentView; } set { _currentView = value; }}
添加PositionX和PositionY两个可绑定属性,用来设置拖拽物的初始位置。当值改变时,将拖拽物的位置设置为新的值。
public static readonly BindableProperty PositionXProperty = BindableProperty.Create("PositionX", typeof(double), typeof(PanContainer), default(double), propertyChanged: (bindable, oldValue, newValue) => { var obj = (PanContainer)bindable; //obj.Content.TranslationX = obj.PositionX; obj.Content.TranslateTo(obj.PositionX, obj.PositionY, 0); });public static readonly BindableProperty PositionYProperty =BindableProperty.Create("PositionY", typeof(double), typeof(PanContainer), default(double), propertyChanged: (bindable, oldValue, newValue) =>{ var obj = (PanContainer)bindable; obj.Content.TranslateTo(obj.PositionX, obj.PositionY, 0); //obj.Content.TranslationY = obj.PositionY;});
订阅PanGestureRecognizer的PanUpdated事件:
private async void PanGestureRecognizer_OnPanUpdated(object sender, PanUpdatedEventArgs e){ var isInPit = false; var isAdsorbInPit = false; switch (e.StatusType) { case GestureStatus.Started: // 手势启动 break; case GestureStatus.Running: // 手势正在运行 break; case GestureStatus.Completed: // 手势完成 break; }}
接下来我们将对手势的各状态:启动、正在运行、已完成的状态做处理
手势开始GestureStatus.Started:手势开始时触发, 触发动画效果,将拖拽物缩小,同时向消息订阅者发送PanType.Start消息。case GestureStatus.Started: Content.Scale=0.5; WeakReferenceMessenger.Default.Send(new PanActionArgs(PanType.Start, this.CurrentView), TokenHelper.PanAction); break;
手势运行GestureStatus.Running:手势正在运行时触发,这个状态下,根据手指在屏幕上的移动距离来计算translationX和translationY,他们是拖拽物在X和Y方向上的移动距离。在X轴方向不超过屏幕的左右边界,即x不得大于this.Width - Content.Width / 2,不得小于 0 - Content.Width / 2
同理在Y轴方向不超过屏幕的上下边界,即y不得大于this.Height - Content.Height / 2,不得小于 0 - Content.Height / 2
代码如下:
case GestureStatus.Running: var translationX = Math.Max(0 - Content.Width / 2, Math.Min(PositionX + e.TotalX, this.Width - Content.Width / 2)); var translationY = Math.Max(0 - Content.Height / 2, Math.Min(PositionY + e.TotalY, this.Height - Content.Height / 2));
接下来判定拖拽物边界
pit的边界是通过Region类来描述的,Region类有四个属性:StartX、EndX、StartY、EndY,分别表示pit的左右边界和上下边界。
public class Region{ public string Name { get; set; } public double StartX { get; set; } public double EndX { get; set; } public double StartY { get; set; } public double EndY { get; set; }}
对PitLayout中的pit进行遍历,判断拖拽物是否在pit内,如果在,则将isInPit设置为true。
判定条件是如果拖拽物的中心位置在pit的边缘内,则认为拖拽物在pit内。
```csharpif (PitLayout != null){ foreach (var item in PitLayout) { var pitRegion = new Region(item.X, item.X + item.Width, item.Y, item.Y + item.Height, item.PitName); var isXin = translationX >= pitRegion.StartX - Content.Width / 2 && translationX <= pitRegion.EndX - Content.Width / 2; var isYin = translationY >= pitRegion.StartY - Content.Height / 2 && translationY <= pitRegion.EndY - Content.Height / 2; if (isYin && isXin) { isInPit = true; if (this.CurrentView == item) { isSwitch = false; } else { if (this.CurrentView != null) { isSwitch = true; } this.CurrentView = item; } } }}
isSwitch是用于检测是否跨过pit,当CurrentView非Null改变时,说明拖拽物跨过了紧挨着的两个pit,需要手动触发PanType.Out和PanType.In消息。
IsInPitPre用于记录在上一次遍历中是否已经发送了PanType.In消息,如果已经发送,则不再重复发送。
if (isInPit){ if (isSwitch) { WeakReferenceMessenger.Default.Send(new PanActionArgs(PanType.Out, this.CurrentView), TokenHelper.PanAction); WeakReferenceMessenger.Default.Send(new PanActionArgs(PanType.In, this.CurrentView), TokenHelper.PanAction); isSwitch = false; } if (!isInPitPre) { WeakReferenceMessenger.Default.Send(new PanActionArgs(PanType.In, this.CurrentView), TokenHelper.PanAction); isInPitPre = true; }}else{ if (isInPitPre) { WeakReferenceMessenger.Default.Send(new PanActionArgs(PanType.Out, this.CurrentView), TokenHelper.PanAction); isInPitPre = false; } this.CurrentView = null;}
最后,将拖拽物控件移动到当前指尖的位置上:
Content.TranslationX = translationX;Content.TranslationY = translationY;break;
手势结束GustureStatus.Completed:手势结束时触发,触发动画效果,将拖拽物放大,同时回弹至原来的位置,最后向消息订阅者发送PanType.Over消息。case GestureStatus.Completed: Content.TranslationX= PositionX; Content.TranslationY= PositionY; Content.Scale= 1; WeakReferenceMessenger.Default.Send(new PanActionArgs(PanType.Over, this.CurrentView), TokenHelper.PanAction); break;
使用控件拖拽物拖拽物可以是任意控件。它将响应手势。在这里定义一个圆形的250*250的半通明黑色BoxView,这个抽象的唱盘就是拖拽物。将响应“平移手势”和“点击手势”
创建pit集合MainPage.xaml中定义一个PitContentLayout
,这个AbsoluteLayout类型的容器控件,内包含一系列控件作为pit,这些pit集合将作为平移手势容器的判断依据。
<--pit控件--> ...
在页面加载完成后,将PitContentLayout中的pit集合赋值给平移手势容器的PitLayout属性。
private async void MainPage_Appearing(object sender, EventArgs e){ this.DefaultPanContainer.PitLayout=this.PitContentLayout.Children.Select(c => c as PitGrid).ToList();}
至此我们完成了平移手势系统的搭建。
这个控件可以拓展到任何检测手指在屏幕上的移动,并可用于将移动应用于内容的用途,例如地图或者图片的平移拖拽等。
项目地址Github:maui-samples
标签:
猜你喜欢

[MAUI 项目实战] 手势控制音乐播放器(二): 手势交互
2023-04-08 18:29:41

全球新资讯:161卡盟为什么登不进去了_161卡盟
2023-04-08 16:49:23

湖南发布人力资源服务业高质量发展行动计划 打造国家级先进制造业人才市场|视焦点讯
2023-04-08 14:59:39

又一女幼师“塌了”,和家长发生不正当关系,聊天尺度很大胆!
2023-04-08 13:00:32

环球消息!急礼品盒主管招聘
2023-04-08 12:14:28

新时代 新征程 新伟业·全力拼经济 各地在行动丨新郑综保区、郑州机场“区港一体化”模式正式启动 “保税+空港”构筑开放新高地
2023-04-08 11:25:09

ReMarkable推出第2代电子纸平板电脑 功能更强大纸质感更强
2023-04-08 10:14:52

如何识别合成祖母绿 如何识别人工合成祖母绿
2023-04-08 08:55:28

观焦点:《杜巴利伯爵夫人》5月16日在戛纳进行世界首映, 法国皇家情事浮出水面
2023-04-08 06:41:49

货船起重机驾驶模拟什么时候出 公测上线时间预告
2023-04-08 03:06:49

tf内存卡怎么连接手机_tf内存卡
2023-04-07 22:40:42

突破500万吨!满洲里口岸一季度进出口货运量创五年来同期新高 环球即时看
2023-04-07 21:05:15

樱桃一天最多能吃多少?
2023-04-07 19:55:57

自然科技2022年净利2305.79万同比减少20.03% 受疫情影响销售低迷-热点在线
2023-04-07 18:46:25

昆药集团党委书记颜炜带队调研西北市场
2023-04-07 17:47:29

这些食物是糖尿病的元凶,你可能每天都在吃!小心变成“小糖人”
2023-04-07 16:59:04

周鸿祎突然离婚 三六零20万股东能否泰然?_全球速递
2023-04-07 16:06:15

天天关注:“五一”出游热度飙升,大理万元套房售空
2023-04-07 14:48:02

网页无法复制粘贴怎么办?网页无法访问如何解决?
2023-04-07 14:52:33

fbinsttool.exe使用说明 如何用FbinstTool制作启动优盘?
2023-04-07 14:52:05

MP3和WAV文件有什么区别?WAV文件有什么优势?
2023-04-07 14:48:44

Socks代理是什么?SocksCap32代理服务器怎么用?
2023-04-07 14:48:17

增强萨满属性是什么?增强萨满要力量还是敏捷?
2023-04-07 14:47:54

cf烟雾头怎么调才清晰?调整cf烟雾头的小技巧分享
2023-04-07 14:45:38

DOS格式化命令是什么?DOS下格式化硬盘操作方法
2023-04-07 14:45:04