国产在线高清精品二区_yw193亚洲中文字幕无码一区_国产精品久久AV无码久久_日韩Aⅴ人妻无码一区二区_上萬網友分享里番全彩之和老师h全彩无码心得

[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ì)象中包含以下屬性:

StatusType,類型 GestureStatus為 ,指示是否為新啟動(dòng)的手勢(shì)、正在運(yùn)行的手勢(shì)、已完成的手勢(shì)或取消的手勢(shì)引發(fā)了事件。TotalX,類型 double為 ,指示自手勢(shì)開始以來 X 方向的總變化。TotalY,類型 double為 ,指示自手勢(shì)開始以來 Y 方向的總變化。容器控件

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)簽:

猜你喜歡