.NET MAUI 中提供了拖放(drag-drop)手勢(shì)識(shí)別器,允許用戶通過(guò)拖動(dòng)手勢(shì)來(lái)移動(dòng)控件。在這篇文章中,我們將學(xué)習(xí)如何使用拖放手勢(shì)識(shí)別器來(lái)實(shí)現(xiàn)可拖拽排序列表。在本例中,列表中顯示不同大小的磁貼(Tile)并且可以拖拽排序。
使用.NET MAU實(shí)現(xiàn)跨平臺(tái)支持,本項(xiàng)目可運(yùn)行于Android、iOS平臺(tái)。
創(chuàng)建可拖放控件
新建.NET MAUI項(xiàng)目,命名Tile
當(dāng)手指觸碰可拖拽區(qū)域超過(guò)一定時(shí)長(zhǎng)(不同平臺(tái)下時(shí)長(zhǎng)不一定相同,如在Android中是1s)時(shí),將觸發(fā)拖動(dòng)手勢(shì)。
手指離開(kāi)屏幕時(shí),將觸發(fā)放置手勢(shì)。
啟用拖動(dòng)
為頁(yè)面視圖控件創(chuàng)建拖動(dòng)手勢(shì)識(shí)別器(DragGestureRecognizer), 它定義了以下屬性:
屬性 | 類型 | 描述 |
---|---|---|
CanDrag | bool | 指明手勢(shì)識(shí)別器附加到的控件能否為拖動(dòng)源。 此屬性的默認(rèn)值為 true。 |
CanDrag | bool | 指明手勢(shì)識(shí)別器附加到的控件能否為拖動(dòng)源。 此屬性的默認(rèn)值為 true。 |
DragStartingCommand | ICommand | 在第一次識(shí)別拖動(dòng)手勢(shì)時(shí)執(zhí)行。 |
DragStartingCommandParameter | object | 是傳遞給 DragStartingCommand 的參數(shù)。 |
DropCompletedCommand | ICommand | 在放置拖動(dòng)源時(shí)執(zhí)行。 |
DropCompletedCommandParameter | object | 是傳遞給 DropCompletedCommand 的參數(shù)。 |
啟用放置
為頁(yè)面視圖控件創(chuàng)建放置手勢(shì)識(shí)別器(DropGestureRecognizer), 它定義了以下屬性:
屬性 | 類型 | 描述 |
---|---|---|
AllowDrop | bool | 指明手勢(shì)識(shí)別器附加到的元素能否為放置目標(biāo)。 此屬性的默認(rèn)值為 true。 |
DragOverCommand | ICommand | 在拖動(dòng)源被拖動(dòng)到放置目標(biāo)上時(shí)執(zhí)行。 |
DragOverCommandParameter | object | 是傳遞給 DragOverCommand 的參數(shù)。 |
DragLeaveCommand | ICommand | 在拖動(dòng)源被拖至放置目標(biāo)上時(shí)執(zhí)行。 |
DragLeaveCommandParameter | object | 是傳遞給 DragLeaveCommand 的參數(shù)。 |
DropCommand | ICommand | 在拖動(dòng)源被放置到放置目標(biāo)上時(shí)執(zhí)行。 |
DropCommandParameter | object | 是傳遞給 DropCommand 的參數(shù)。 |
創(chuàng)建可拖拽控件的綁定類,實(shí)現(xiàn)IDraggableItem接口,定義拖動(dòng)相關(guān)的屬性和命令。
public interface IDraggableItem
{
bool IsBeingDraggedOver { get; set; }
bool IsBeingDragged { get; set; }
Command Dragged { get; set; }
Command DraggedOver { get; set; }
Command DragLeave { get; set; }
Command Dropped { get; set; }
object DraggedItem { get; set; }
object DropPlaceHolderItem { get; set; }
}
Dragged: 拖拽開(kāi)始時(shí)觸發(fā)的命令。
DraggedOver: 拖拽控件懸停在當(dāng)前控件上方時(shí)觸發(fā)的命令。
DragLeave: 拖拽控件離開(kāi)當(dāng)前控件時(shí)觸發(fā)的命令。
Dropped: 拖拽控件放置在當(dāng)前控件上方時(shí)觸發(fā)的命令。
IsBeingDragged 為true時(shí),通知當(dāng)前控件正在被拖拽。
IsBeingDraggedOver 為true時(shí),通知當(dāng)前控件正在有拖拽控件懸停在其上方。
DraggedItem: 正在拖拽的控件。
DropPlaceHolderItem: 懸停在其上方時(shí)的控件,即當(dāng)前控件的占位控件。
此時(shí)可拖拽控件為磁貼片段(TileSegement), 創(chuàng)建一個(gè)類用于描述磁貼可顯示的屬性,如標(biāo)題、描述、圖標(biāo)、顏色等。
public class TileSegment
{
public string Title { get; set; }
public string Type { get; set; }
public string Desc { get; set; }
public string Icon { get; set; }
public Color Color { get; set; }
}
創(chuàng)建綁定服務(wù)類
創(chuàng)建可拖拽控件的綁定服務(wù)類TileSegmentService,繼承ObservableObject,并實(shí)現(xiàn)IDraggableItem接口。
public class TileSegmentService : ObservableObject, ITileSegmentService
{
...
}
拖拽(Drag)
拖拽開(kāi)始時(shí),將IsBeingDragged設(shè)置為true,通知當(dāng)前控件正在被拖拽,同時(shí)將DraggedItem設(shè)置為當(dāng)前控件。
private void OnDragged(object item)
{
IsBeingDragged=true;
DraggedItem=item;
}
拖拽懸停,經(jīng)過(guò)(DragOver)
拖拽控件懸停在當(dāng)前控件上方時(shí),將IsBeingDraggedOver設(shè)置為true,通知當(dāng)前控件正在有拖拽控件懸停在其上方,同時(shí)在服務(wù)列表中尋找當(dāng)前正在被拖拽的服務(wù),將DropPlaceHolderItem設(shè)置為當(dāng)前控件。
private void OnDraggedOver(object item)
{
if (!IsBeingDragged && item!=null)
{
IsBeingDraggedOver=true;
var itemToMove = Container.TileSegments.First(i => i.IsBeingDragged);
if (itemToMove.DraggedItem!=null)
{
DropPlaceHolderItem=itemToMove.DraggedItem;
}
}
}
離開(kāi)控件上方時(shí),IsBeingDraggedOver設(shè)置為false
private void OnDragLeave(object item)
{
IsBeingDraggedOver = false;
}
釋放(Drop)
拖拽完成時(shí),獲取當(dāng)前正在被拖拽的控件,將其從服務(wù)列表中移除,然后將其插入到當(dāng)前控件的位置,通知當(dāng)前控件拖拽完成。
private void OnDropped(object item)
{
var itemToMove = Container.TileSegments.First(i => i.IsBeingDragged);
if (itemToMove == null || itemToMove == this)
return;
Container.TileSegments.Remove(itemToMove);
var insertAtIndex = Container.TileSegments.IndexOf(this);
Container.TileSegments.Insert(insertAtIndex, itemToMove);
itemToMove.IsBeingDragged = false;
IsBeingDraggedOver = false;
DraggedItem=null;
}
完整的TileSegmentService代碼如下:
public class TileSegmentService : ObservableObject, ITileSegmentService
{
public TileSegmentService(
TileSegment tileSegment)
{
Remove = new Command(RemoveAction);
TileSegment = tileSegment;
Dragged = new Command(OnDragged);
DraggedOver = new Command(OnDraggedOver);
DragLeave = new Command(OnDragLeave);
Dropped = new Command(i => OnDropped(i));
}
private void OnDragged(object item)
{
IsBeingDragged=true;
}
private void OnDraggedOver(object item)
{
if (!IsBeingDragged && item!=null)
{
IsBeingDraggedOver=true;
var itemToMove = Container.TileSegments.First(i => i.IsBeingDragged);
if (itemToMove.DraggedItem!=null)
{
DropPlaceHolderItem=itemToMove.DraggedItem;
}
}
}
private object _draggedItem;
public object DraggedItem
{
get { return _draggedItem; }
set
{
_draggedItem = value;
OnPropertyChanged();
}
}
private object _dropPlaceHolderItem;
public object DropPlaceHolderItem
{
get { return _dropPlaceHolderItem; }
set
{
_dropPlaceHolderItem = value;
OnPropertyChanged();
}
}
private void OnDragLeave(object item)
{
IsBeingDraggedOver = false;
DraggedItem = null;
}
private void OnDropped(object item)
{
var itemToMove = Container.TileSegments.First(i => i.IsBeingDragged);
if (itemToMove == null || itemToMove == this)
return;
Container.TileSegments.Remove(itemToMove);
var insertAtIndex = Container.TileSegments.IndexOf(this);
Container.TileSegments.Insert(insertAtIndex, itemToMove);
itemToMove.IsBeingDragged = false;
IsBeingDraggedOver = false;
DraggedItem=null;
}
private async void RemoveAction(object obj)
{
if (Container is ITileSegmentServiceContainer)
{
(Container as ITileSegmentServiceContainer).RemoveSegment.Execute(this);
}
}
public IReadOnlyTileSegmentServiceContainer Container { get; set; }
private TileSegment tileSegment;
public TileSegment TileSegment
{
get { return tileSegment; }
set
{
tileSegment = value;
OnPropertyChanged();
}
}
private bool _isBeingDragged;
public bool IsBeingDragged
{
get { return _isBeingDragged; }
set
{
_isBeingDragged = value;
OnPropertyChanged();
}
}
private bool _isBeingDraggedOver;
public bool IsBeingDraggedOver
{
get { return _isBeingDraggedOver; }
set
{
_isBeingDraggedOver = value;
OnPropertyChanged();
}
}
public Command Remove { get; set; }
public Command Dragged { get; set; }
public Command DraggedOver { get; set; }
public Command DragLeave { get; set; }
public Command Dropped { get; set; }
}
創(chuàng)建頁(yè)面元素
在Controls目錄下創(chuàng)建不同大小的磁貼控件,如下圖所示。
在MainPage中創(chuàng)建CollectionView,用于將磁貼元素以列表形式展示。
<CollectionView Grid.Row="1"
x:Name="MainCollectionView"
ItemsSource="{Binding TileSegments}"
ItemTemplate="{StaticResource TileSegmentDataTemplateSelector}">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" />
</CollectionView.ItemsLayout>
</CollectionView>
創(chuàng)建MainPageViewModel,創(chuàng)建綁定服務(wù)類集合TileSegments,初始化中添加一些不同顏色,大小的磁貼,并將TileSegementService.Container設(shè)置為自己(this)。
不同大小的磁貼通過(guò)綁定相應(yīng)的數(shù)據(jù),使用不同的數(shù)據(jù)模板進(jìn)行展示。請(qǐng)閱讀博文 [MAUI程序設(shè)計(jì)]界面多態(tài)與實(shí)現(xiàn),了解如何實(shí)現(xiàn)列表Item的多態(tài)。
在MainPage中創(chuàng)建磁貼片段數(shù)據(jù)模板選擇器(TileSegmentDataTemplateSelector),用于根據(jù)磁貼片段的大小選擇不同的數(shù)據(jù)模板。
<DataTemplate x:Key="SmallSegment">
<controls1:SmallSegmentView Margin="0,5"
ControlTemplate="{StaticResource TileSegmentTemplate}">
</controls1:SmallSegmentView>
</DataTemplate>
<DataTemplate x:Key="MediumSegment">
<controls1:MediumSegmentView Margin="0,5"
ControlTemplate="{StaticResource TileSegmentTemplate}">
</controls1:MediumSegmentView>
</DataTemplate>
<DataTemplate x:Key="LargeSegment">
<controls1:LargeSegmentView Margin="0,5"
ControlTemplate="{StaticResource TileSegmentTemplate}">
</controls1:LargeSegmentView>
</DataTemplate>
<controls1:TileSegmentDataTemplateSelector x:Key="TileSegmentDataTemplateSelector"
ResourcesContainer="{x:Reference Main}" />
創(chuàng)建磁貼控件模板TileSegmentTemplate,并在此指定DropGestureRecognizer
<ControlTemplate x:Key="TileSegmentTemplate">
<ContentView>
<StackLayout>
<StackLayout.GestureRecognizers>
<DropGestureRecognizer AllowDrop="True"
DragLeaveCommand="{TemplateBinding BindingContext.DragLeave}"
DragLeaveCommandParameter="{TemplateBinding}"
DragOverCommand="{TemplateBinding BindingContext.DraggedOver}"
DragOverCommandParameter="{TemplateBinding}"
DropCommand="{TemplateBinding BindingContext.Dropped}"
DropCommandParameter="{TemplateBinding}" />
</StackLayout.GestureRecognizers>
</StackLayout>
</ContentView>
</ControlTemplate>
創(chuàng)建磁貼控件外觀Layout,<ContentPresenter />
處將呈現(xiàn)磁貼片段的內(nèi)容。在Layout指定DragGestureRecognizer。
<Border x:Name="ContentLayout"
Margin="0">
<Grid>
<Grid.GestureRecognizers>
<DragGestureRecognizer CanDrag="True"
DragStartingCommand="{TemplateBinding BindingContext.Dragged}"
DragStartingCommandParameter="{TemplateBinding}" />
</Grid.GestureRecognizers>
<ContentPresenter />
<Button CornerRadius="100"
HeightRequest="20"
WidthRequest="20"
Padding="0"
BackgroundColor="Red"
TextColor="White"
Command="{TemplateBinding BindingContext.Remove}"
Text="×"
HorizontalOptions="End"
VerticalOptions="Start"></Button>
</Grid>
</Border>
創(chuàng)建占位控件,用于指示松開(kāi)手指時(shí),控件將放置的位置區(qū)域,在這里綁定DropPlaceHolderItem的高度和寬度。
<Border StrokeThickness="4"
StrokeDashArray="2 2"
StrokeDashOffset="6"
Stroke="black"
HorizontalOptions="Center"
IsVisible="{TemplateBinding BindingContext.IsBeingDraggedOver}">
<Grid HeightRequest="{TemplateBinding BindingContext.DropPlaceHolderItem.Height}"
WidthRequest="{TemplateBinding BindingContext.DropPlaceHolderItem.Width}">
<Label HorizontalTextAlignment="Center"
VerticalOptions="Center"
Text="松開(kāi)手指將放置條目至此處"></Label>
</Grid>
</Border>
最終效果
項(xiàng)目地址
Github:maui-samples文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-652027.html
關(guān)注我,學(xué)習(xí)更多.NET MAUI開(kāi)發(fā)知識(shí)!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-652027.html
到了這里,關(guān)于[MAUI]在.NET MAUI中實(shí)現(xiàn)可拖拽排序列表的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!