[MAUI 項(xiàng)目實(shí)戰(zhàn)] 手勢(shì)控制音樂播放器(二): 手勢(shì)交互
來源: 博客園 2023-04-08 18:29:41
@
目錄原理交互實(shí)現(xiàn)容器控件手勢(shì)開始手勢(shì)運(yùn)行手勢(shì)結(jié)束使用控件拖拽物創(chuàng)建pit集合項(xiàng)目地址原理定義一個(gè)拖拽物,和它拖拽的目標(biāo),拖拽物可以理解為一個(gè)平底鍋(pan),拖拽目標(biāo)是一個(gè)坑(pit),當(dāng)拖拽物進(jìn)入坑時(shí),拖拽物就會(huì)被吸附在坑里??梢阅X補(bǔ)一下下圖:
你問我為什么是平底鍋和坑,當(dāng)然了在微軟官方的寫法里pan是平移的意思,而不是指代平底鍋。只是通過同義詞來方便理解坑就是正好是平底鍋大小的爐灶。正好可以放入平底鍋。
(資料圖片僅供參考)
pan和pit組成平移手勢(shì)的系統(tǒng),在具體代碼中包含了邊緣檢測(cè)判定和狀態(tài)機(jī)維護(hù)。我們將一步步實(shí)現(xiàn)平移手勢(shì)功能
pit很簡(jiǎn)單,是一個(gè)包含了名稱屬性的控件,這個(gè)名稱屬性是用來標(biāo)識(shí)pit的。以便當(dāng)pan入坑時(shí)我們知道入了哪個(gè)坑,IsEnable是一個(gè)綁定屬性,它用來控制pit是否可用的。
在這個(gè)程序中,拖拽物是一個(gè)抽象的唱盤。它的拖拽目標(biāo)是周圍8個(gè)圖標(biāo)。
交互實(shí)現(xiàn)這里用Grid作為pit控件基類型,因?yàn)镚rid可以包含子控件,我們可以在pit控件中添加子控件,比如一個(gè)圖片,一個(gè)文字,這樣就可以讓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的交互信息。
定義一個(gè)平移事件PanAction,在pan和pit產(chǎn)生交匯時(shí)觸發(fā)。其參數(shù)PanActionArgs描述了pan和pit的交互的關(guān)系和狀態(tài)。
public class PanActionArgs{ public PanActionArgs(PanType type, PitGrid pit = null) { PanType = type; CurrentPit = pit; } public PanType PanType { get; set; } public PitGrid CurrentPit { get; set; }}
手勢(shì)狀態(tài)類型PanType定義如下:
In:pan進(jìn)入pit時(shí)觸發(fā),Out:pan離開pit時(shí)觸發(fā),Over:釋放pan時(shí)觸發(fā),·Start:pan開始拖拽時(shí)觸發(fā)public enum PanType{ Out, In, Over, Start}
MAUI為我們開發(fā)者包裝好了PanGestureRecognizer
即平移手勢(shì)識(shí)別器。
平移手勢(shì)更改時(shí)引發(fā)事件PanUpdated
事件,此事件附帶的 PanUpdatedEventArgs對(duì)象中包含以下屬性:
PanGestureRecognizer
提供了當(dāng)手指在屏幕移動(dòng)這一過程的描述我們需要一個(gè)容器控件來對(duì)拖拽物進(jìn)行包裝,以賦予拖拽物響應(yīng)平移手勢(shì)的能力。
創(chuàng)建平移手勢(shì)容器控件:在Controls目錄中新建PanContainer.xaml,代碼如下:
為PanContainer添加PitLayout屬性,用來存放pit的集合。打開PanContainer.xaml.cs,添加如下代碼:
private IList _pitLayout;public IList PitLayout{ get { return _pitLayout; } set { _pitLayout = value; }}
CurrentView屬性為當(dāng)前拖拽物所在的pit控件。
private PitGrid _currentView;public PitGrid CurrentView{ get { return _currentView; } set { _currentView = value; }}
添加PositionX和PositionY兩個(gè)可綁定屬性,用來設(shè)置拖拽物的初始位置。當(dāng)值改變時(shí),將拖拽物的位置設(shè)置為新的值。
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: // 手勢(shì)啟動(dòng) break; case GestureStatus.Running: // 手勢(shì)正在運(yùn)行 break; case GestureStatus.Completed: // 手勢(shì)完成 break; }}
接下來我們將對(duì)手勢(shì)的各狀態(tài):?jiǎn)?dòng)、正在運(yùn)行、已完成的狀態(tài)做處理
手勢(shì)開始GestureStatus.Started:手勢(shì)開始時(shí)觸發(fā), 觸發(fā)動(dòng)畫效果,將拖拽物縮小,同時(shí)向消息訂閱者發(fā)送PanType.Start消息。case GestureStatus.Started: Content.Scale=0.5; WeakReferenceMessenger.Default.Send(new PanActionArgs(PanType.Start, this.CurrentView), TokenHelper.PanAction); break;
手勢(shì)運(yùn)行GestureStatus.Running:手勢(shì)正在運(yùn)行時(shí)觸發(fā),這個(gè)狀態(tài)下,根據(jù)手指在屏幕上的移動(dòng)距離來計(jì)算translationX和translationY,他們是拖拽物在X和Y方向上的移動(dòng)距離。在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類有四個(gè)屬性: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; }}
對(duì)PitLayout中的pit進(jìn)行遍歷,判斷拖拽物是否在pit內(nèi),如果在,則將isInPit設(shè)置為true。
判定條件是如果拖拽物的中心位置在pit的邊緣內(nèi),則認(rèn)為拖拽物在pit內(nèi)。
```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是用于檢測(cè)是否跨過pit,當(dāng)CurrentView非Null改變時(shí),說明拖拽物跨過了緊挨著的兩個(gè)pit,需要手動(dòng)觸發(fā)PanType.Out和PanType.In消息。
IsInPitPre用于記錄在上一次遍歷中是否已經(jīng)發(fā)送了PanType.In消息,如果已經(jīng)發(fā)送,則不再重復(fù)發(fā)送。
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;}
最后,將拖拽物控件移動(dòng)到當(dāng)前指尖的位置上:
Content.TranslationX = translationX;Content.TranslationY = translationY;break;
手勢(shì)結(jié)束GustureStatus.Completed:手勢(shì)結(jié)束時(shí)觸發(fā),觸發(fā)動(dòng)畫效果,將拖拽物放大,同時(shí)回彈至原來的位置,最后向消息訂閱者發(fā)送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;
使用控件拖拽物拖拽物可以是任意控件。它將響應(yīng)手勢(shì)。在這里定義一個(gè)圓形的250*250的半通明黑色BoxView,這個(gè)抽象的唱盤就是拖拽物。將響應(yīng)“平移手勢(shì)”和“點(diǎn)擊手勢(shì)”
創(chuàng)建pit集合MainPage.xaml中定義一個(gè)PitContentLayout
,這個(gè)AbsoluteLayout類型的容器控件,內(nèi)包含一系列控件作為pit,這些pit集合將作為平移手勢(shì)容器的判斷依據(jù)。
<--pit控件--> ...
在頁(yè)面加載完成后,將PitContentLayout中的pit集合賦值給平移手勢(shì)容器的PitLayout屬性。
private async void MainPage_Appearing(object sender, EventArgs e){ this.DefaultPanContainer.PitLayout=this.PitContentLayout.Children.Select(c => c as PitGrid).ToList();}
至此我們完成了平移手勢(shì)系統(tǒng)的搭建。
這個(gè)控件可以拓展到任何檢測(cè)手指在屏幕上的移動(dòng),并可用于將移動(dòng)應(yīng)用于內(nèi)容的用途,例如地圖或者圖片的平移拖拽等。
項(xiàng)目地址Github:maui-samples
標(biāo)簽:
猜你喜歡

[MAUI 項(xiàng)目實(shí)戰(zhàn)] 手勢(shì)控制音樂播放器(二): 手勢(shì)交互
2023-04-08 18:29:41

全球新資訊:161卡盟為什么登不進(jìn)去了_161卡盟
2023-04-08 16:49:23

湖南發(fā)布人力資源服務(wù)業(yè)高質(zhì)量發(fā)展行動(dòng)計(jì)劃 打造國(guó)家級(jí)先進(jìn)制造業(yè)人才市場(chǎng)|視焦點(diǎn)訊
2023-04-08 14:59:39

又一女幼師“塌了”,和家長(zhǎng)發(fā)生不正當(dāng)關(guān)系,聊天尺度很大膽!
2023-04-08 13:00:32

環(huán)球消息!急禮品盒主管招聘
2023-04-08 12:14:28

新時(shí)代 新征程 新偉業(yè)·全力拼經(jīng)濟(jì) 各地在行動(dòng)丨新鄭綜保區(qū)、鄭州機(jī)場(chǎng)“區(qū)港一體化”模式正式啟動(dòng) “保稅+空港”構(gòu)筑開放新高地
2023-04-08 11:25:09

ReMarkable推出第2代電子紙平板電腦 功能更強(qiáng)大紙質(zhì)感更強(qiáng)
2023-04-08 10:14:52

如何識(shí)別合成祖母綠 如何識(shí)別人工合成祖母綠
2023-04-08 08:55:28

觀焦點(diǎn):《杜巴利伯爵夫人》5月16日在戛納進(jìn)行世界首映, 法國(guó)皇家情事浮出水面
2023-04-08 06:41:49

貨船起重機(jī)駕駛模擬什么時(shí)候出 公測(cè)上線時(shí)間預(yù)告
2023-04-08 03:06:49

tf內(nèi)存卡怎么連接手機(jī)_tf內(nèi)存卡
2023-04-07 22:40:42

突破500萬噸!滿洲里口岸一季度進(jìn)出口貨運(yùn)量創(chuàng)五年來同期新高 環(huán)球即時(shí)看
2023-04-07 21:05:15

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

自然科技2022年凈利2305.79萬同比減少20.03% 受疫情影響銷售低迷-熱點(diǎn)在線
2023-04-07 18:46:25

昆藥集團(tuán)黨委書記顏煒帶隊(duì)調(diào)研西北市場(chǎng)
2023-04-07 17:47:29

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

周鴻祎突然離婚 三六零20萬股東能否泰然?_全球速遞
2023-04-07 16:06:15

天天關(guān)注:“五一”出游熱度飆升,大理萬元套房售空
2023-04-07 14:48:02

網(wǎng)頁(yè)無法復(fù)制粘貼怎么辦?網(wǎng)頁(yè)無法訪問如何解決?
2023-04-07 14:52:33

fbinsttool.exe使用說明 如何用FbinstTool制作啟動(dòng)優(yōu)盤?
2023-04-07 14:52:05

MP3和WAV文件有什么區(qū)別?WAV文件有什么優(yōu)勢(shì)?
2023-04-07 14:48:44

Socks代理是什么?SocksCap32代理服務(wù)器怎么用?
2023-04-07 14:48:17

增強(qiáng)薩滿屬性是什么?增強(qiáng)薩滿要力量還是敏捷?
2023-04-07 14:47:54

cf煙霧頭怎么調(diào)才清晰?調(diào)整cf煙霧頭的小技巧分享
2023-04-07 14:45:38

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