.NET MAUI 中提供了拖放(drag-drop)手势识别器,允许用户通过拖动手势来移动控件。在这篇文章中,我们将学习如何使用拖放手势识别器来实现可拖拽排序列表。在本例中,列表中显示不同大小的磁贴(Tile)并且可以拖拽排序。
使用.NET MAU实现跨平台支持,本项目可运行于Android、iOS平台。
新建.NET MAUI项目,命名Tile
当手指触碰可拖拽区域超过一定时长(不同平台下时长不一定相同,如在Android中是1s)时,将触发拖动手势。 手指离开屏幕时,将触发放置手势。
启用拖动
为页面视图控件创建拖动手势识别器(DragGestureRecognizer), 它定义了以下属性:
启用放置
为页面视图控件创建放置手势识别器(DropGestureRecognizer), 它定义了以下属性:
创建可拖拽控件的绑定类,实现IDraggableItem接口,定义拖动相关的属性和命令。
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; }}
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: 拖拽开始时触发的命令。 DraggedOver: 拖拽控件悬停在当前控件上方时触发的命令。 DragLeave: 拖拽控件离开当前控件时触发的命令。 Dropped: 拖拽控件放置在当前控件上方时触发的命令。
IsBeingDragged 为true时,通知当前控件正在被拖拽。 IsBeingDraggedOver 为true时,通知当前控件正在有拖拽控件悬停在其上方。
DraggedItem: 正在拖拽的控件。 DropPlaceHolderItem: 悬停在其上方时的控件,即当前控件的占位控件。
此时可拖拽控件为磁贴片段(TileSegement), 创建一个类用于描述磁贴可显示的属性,如标题、描述、图标、颜色等。
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; }}
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; }
创建可拖拽控件的绑定服务类TileSegmentService,继承ObservableObject,并实现IDraggableItem接口。
public class TileSegmentService : ObservableObject, ITileSegmentService{ ...}
public class TileSegmentService : ObservableObject, ITileSegmentService
...
拖拽开始时,将IsBeingDragged设置为true,通知当前控件正在被拖拽,同时将DraggedItem设置为当前控件。
private void OnDragged(object item){ IsBeingDragged=true; DraggedItem=item;}
private void OnDragged(object item)
IsBeingDragged=true;
DraggedItem=item;
拖拽控件悬停在当前控件上方时,将IsBeingDraggedOver设置为true,通知当前控件正在有拖拽控件悬停在其上方,同时在服务列表中寻找当前正在被拖拽的服务,将DropPlaceHolderItem设置为当前控件。
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 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;
离开控件上方时,IsBeingDraggedOver设置为false
private void OnDragLeave(object item){ IsBeingDraggedOver = false;}
private void OnDragLeave(object item)
IsBeingDraggedOver = false;
拖拽完成时,获取当前正在被拖拽的控件,将其从服务列表中移除,然后将其插入到当前控件的位置,通知当前控件拖拽完成。
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 void OnDropped(object item)
if (itemToMove == null || itemToMove == this)
return;
Container.TileSegments.Remove(itemToMove);
var insertAtIndex = Container.TileSegments.IndexOf(this);
Container.TileSegments.Insert(insertAtIndex, itemToMove);
itemToMove.IsBeingDragged = 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; }}
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 object _draggedItem;
public object DraggedItem
get { return _draggedItem; }
set
_draggedItem = value;
OnPropertyChanged();
private object _dropPlaceHolderItem;
public object DropPlaceHolderItem
get { return _dropPlaceHolderItem; }
_dropPlaceHolderItem = value;
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; }
tileSegment = value;
private bool _isBeingDragged;
public bool IsBeingDragged
get { return _isBeingDragged; }
_isBeingDragged = value;
private bool _isBeingDraggedOver;
public bool IsBeingDraggedOver
get { return _isBeingDraggedOver; }
_isBeingDraggedOver = value;
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; }
在Controls目录下创建不同大小的磁贴控件,如下图所示。
在MainPage中创建CollectionView,用于将磁贴元素以列表形式展示。
<CollectionView Grid.Row="1" x:Name="MainCollectionView" ItemsSource="{Binding TileSegments}" ItemTemplate="{StaticResource TileSegmentDataTemplateSelector}"> <CollectionView.ItemsLayout> <LinearItemsLayout Orientation="Vertical" /> </CollectionView.ItemsLayout></CollectionView>
<CollectionView Grid.Row="1"
x:Name="MainCollectionView"
ItemsSource="{Binding TileSegments}"
ItemTemplate="{StaticResource TileSegmentDataTemplateSelector}">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" />
</CollectionView.ItemsLayout>
</CollectionView>
创建MainPageViewModel,创建绑定服务类集合TileSegments,初始化中添加一些不同颜色,大小的磁贴,并将TileSegementService.Container设置为自己(this)。
不同大小的磁贴通过绑定相应的数据,使用不同的数据模板进行展示。请阅读博文 [MAUI程序设计]界面多态与实现,了解如何实现列表Item的多态。
在MainPage中创建磁贴片段数据模板选择器(TileSegmentDataTemplateSelector),用于根据磁贴片段的大小选择不同的数据模板。
<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}" />
<DataTemplate x:Key="SmallSegment">
<controls1:SmallSegmentView Margin="0,5"
ControlTemplate="{StaticResource TileSegmentTemplate}">
</controls1:SmallSegmentView>
</DataTemplate>
<DataTemplate x:Key="MediumSegment">
<controls1:MediumSegmentView Margin="0,5"
</controls1:MediumSegmentView>
<DataTemplate x:Key="LargeSegment">
<controls1:LargeSegmentView Margin="0,5"
</controls1:LargeSegmentView>
<controls1:TileSegmentDataTemplateSelector x:Key="TileSegmentDataTemplateSelector"
ResourcesContainer="{x:Reference Main}" />
创建磁贴控件模板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>
<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>
创建磁贴控件外观Layout,<ContentPresenter />处将呈现磁贴片段的内容。在Layout指定DragGestureRecognizer。
<ContentPresenter />
<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>
<Border x:Name="ContentLayout"
Margin="0">
<Grid>
<Grid.GestureRecognizers>
<DragGestureRecognizer CanDrag="True"
DragStartingCommand="{TemplateBinding BindingContext.Dragged}"
DragStartingCommandParameter="{TemplateBinding}" />
</Grid.GestureRecognizers>
<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>
创建占位控件,用于指示松开手指时,控件将放置的位置区域,在这里绑定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="松开手指将放置条目至此处"></Label> </Grid></Border>
<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="松开手指将放置条目至此处"></Label>
Github:maui-samples
关注我,学习更多.NET MAUI开发知识!
本文来自博客园,作者:林晓lx,转载请注明原文链接:https://www.cnblogs.com/jevonsflash/p/17631233.html
原文链接:https://www.cnblogs.com/jevonsflash/p/17631233.html
本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728