经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
[MAUI]在.NET MAUI中实现可拖拽排序列表
来源:cnblogs  作者:林晓lx  时间:2023/8/16 9:14:28  对本文有异议

.NET MAUI 中提供了拖放(drag-drop)手势识别器,允许用户通过拖动手势来移动控件。在这篇文章中,我们将学习如何使用拖放手势识别器来实现可拖拽排序列表。在本例中,列表中显示不同大小的磁贴(Tile)并且可以拖拽排序。

在这里插入图片描述

使用.NET MAU实现跨平台支持,本项目可运行于Android、iOS平台。

创建可拖放控件

新建.NET MAUI项目,命名Tile

当手指触碰可拖拽区域超过一定时长(不同平台下时长不一定相同,如在Android中是1s)时,将触发拖动手势。
手指离开屏幕时,将触发放置手势。

启用拖动

为页面视图控件创建拖动手势识别器(DragGestureRecognizer), 它定义了以下属性:

属性 类型 描述
CanDrag bool 指明手势识别器附加到的控件能否为拖动源。 此属性的默认值为 true。
CanDrag bool 指明手势识别器附加到的控件能否为拖动源。 此属性的默认值为 true。
DragStartingCommand ICommand 在第一次识别拖动手势时执行。
DragStartingCommandParameter object 是传递给 DragStartingCommand 的参数。
DropCompletedCommand ICommand 在放置拖动源时执行。
DropCompletedCommandParameter object 是传递给 DropCompletedCommand 的参数。

启用放置

为页面视图控件创建放置手势识别器(DropGestureRecognizer), 它定义了以下属性:

属性 类型 描述
AllowDrop bool 指明手势识别器附加到的元素能否为放置目标。 此属性的默认值为 true。
DragOverCommand ICommand 在拖动源被拖动到放置目标上时执行。
DragOverCommandParameter object 是传递给 DragOverCommand 的参数。
DragLeaveCommand ICommand 在拖动源被拖至放置目标上时执行。
DragLeaveCommandParameter object 是传递给 DragLeaveCommand 的参数。
DropCommand ICommand 在拖动源被放置到放置目标上时执行。
DropCommandParameter object 是传递给 DropCommand 的参数。

创建可拖拽控件的绑定类,实现IDraggableItem接口,定义拖动相关的属性和命令。

  1. public interface IDraggableItem
  2. {
  3. bool IsBeingDraggedOver { get; set; }
  4. bool IsBeingDragged { get; set; }
  5. Command Dragged { get; set; }
  6. Command DraggedOver { get; set; }
  7. Command DragLeave { get; set; }
  8. Command Dropped { get; set; }
  9. object DraggedItem { get; set; }
  10. object DropPlaceHolderItem { get; set; }
  11. }

Dragged: 拖拽开始时触发的命令。
DraggedOver: 拖拽控件悬停在当前控件上方时触发的命令。
DragLeave: 拖拽控件离开当前控件时触发的命令。
Dropped: 拖拽控件放置在当前控件上方时触发的命令。

IsBeingDragged 为true时,通知当前控件正在被拖拽。
IsBeingDraggedOver 为true时,通知当前控件正在有拖拽控件悬停在其上方。

DraggedItem: 正在拖拽的控件。
DropPlaceHolderItem: 悬停在其上方时的控件,即当前控件的占位控件。

此时可拖拽控件为磁贴片段(TileSegement), 创建一个类用于描述磁贴可显示的属性,如标题、描述、图标、颜色等。

  1. public class TileSegment
  2. {
  3. public string Title { get; set; }
  4. public string Type { get; set; }
  5. public string Desc { get; set; }
  6. public string Icon { get; set; }
  7. public Color Color { get; set; }
  8. }

创建绑定服务类

创建可拖拽控件的绑定服务类TileSegmentService,继承ObservableObject,并实现IDraggableItem接口。

  1. public class TileSegmentService : ObservableObject, ITileSegmentService
  2. {
  3. ...
  4. }

拖拽(Drag)

拖拽开始时,将IsBeingDragged设置为true,通知当前控件正在被拖拽,同时将DraggedItem设置为当前控件。

  1. private void OnDragged(object item)
  2. {
  3. IsBeingDragged=true;
  4. DraggedItem=item;
  5. }

拖拽悬停,经过(DragOver)

拖拽控件悬停在当前控件上方时,将IsBeingDraggedOver设置为true,通知当前控件正在有拖拽控件悬停在其上方,同时在服务列表中寻找当前正在被拖拽的服务,将DropPlaceHolderItem设置为当前控件。

  1. private void OnDraggedOver(object item)
  2. {
  3. if (!IsBeingDragged && item!=null)
  4. {
  5. IsBeingDraggedOver=true;
  6. var itemToMove = Container.TileSegments.First(i => i.IsBeingDragged);
  7. if (itemToMove.DraggedItem!=null)
  8. {
  9. DropPlaceHolderItem=itemToMove.DraggedItem;
  10. }
  11. }
  12. }

离开控件上方时,IsBeingDraggedOver设置为false

  1. private void OnDragLeave(object item)
  2. {
  3. IsBeingDraggedOver = false;
  4. }

释放(Drop)

拖拽完成时,获取当前正在被拖拽的控件,将其从服务列表中移除,然后将其插入到当前控件的位置,通知当前控件拖拽完成。

  1. private void OnDropped(object item)
  2. {
  3. var itemToMove = Container.TileSegments.First(i => i.IsBeingDragged);
  4. if (itemToMove == null || itemToMove == this)
  5. return;
  6. Container.TileSegments.Remove(itemToMove);
  7. var insertAtIndex = Container.TileSegments.IndexOf(this);
  8. Container.TileSegments.Insert(insertAtIndex, itemToMove);
  9. itemToMove.IsBeingDragged = false;
  10. IsBeingDraggedOver = false;
  11. DraggedItem=null;
  12. }

完整的TileSegmentService代码如下:

  1. public class TileSegmentService : ObservableObject, ITileSegmentService
  2. {
  3. public TileSegmentService(
  4. TileSegment tileSegment)
  5. {
  6. Remove = new Command(RemoveAction);
  7. TileSegment = tileSegment;
  8. Dragged = new Command(OnDragged);
  9. DraggedOver = new Command(OnDraggedOver);
  10. DragLeave = new Command(OnDragLeave);
  11. Dropped = new Command(i => OnDropped(i));
  12. }
  13. private void OnDragged(object item)
  14. {
  15. IsBeingDragged=true;
  16. }
  17. private void OnDraggedOver(object item)
  18. {
  19. if (!IsBeingDragged && item!=null)
  20. {
  21. IsBeingDraggedOver=true;
  22. var itemToMove = Container.TileSegments.First(i => i.IsBeingDragged);
  23. if (itemToMove.DraggedItem!=null)
  24. {
  25. DropPlaceHolderItem=itemToMove.DraggedItem;
  26. }
  27. }
  28. }
  29. private object _draggedItem;
  30. public object DraggedItem
  31. {
  32. get { return _draggedItem; }
  33. set
  34. {
  35. _draggedItem = value;
  36. OnPropertyChanged();
  37. }
  38. }
  39. private object _dropPlaceHolderItem;
  40. public object DropPlaceHolderItem
  41. {
  42. get { return _dropPlaceHolderItem; }
  43. set
  44. {
  45. _dropPlaceHolderItem = value;
  46. OnPropertyChanged();
  47. }
  48. }
  49. private void OnDragLeave(object item)
  50. {
  51. IsBeingDraggedOver = false;
  52. DraggedItem = null;
  53. }
  54. private void OnDropped(object item)
  55. {
  56. var itemToMove = Container.TileSegments.First(i => i.IsBeingDragged);
  57. if (itemToMove == null || itemToMove == this)
  58. return;
  59. Container.TileSegments.Remove(itemToMove);
  60. var insertAtIndex = Container.TileSegments.IndexOf(this);
  61. Container.TileSegments.Insert(insertAtIndex, itemToMove);
  62. itemToMove.IsBeingDragged = false;
  63. IsBeingDraggedOver = false;
  64. DraggedItem=null;
  65. }
  66. private async void RemoveAction(object obj)
  67. {
  68. if (Container is ITileSegmentServiceContainer)
  69. {
  70. (Container as ITileSegmentServiceContainer).RemoveSegment.Execute(this);
  71. }
  72. }
  73. public IReadOnlyTileSegmentServiceContainer Container { get; set; }
  74. private TileSegment tileSegment;
  75. public TileSegment TileSegment
  76. {
  77. get { return tileSegment; }
  78. set
  79. {
  80. tileSegment = value;
  81. OnPropertyChanged();
  82. }
  83. }
  84. private bool _isBeingDragged;
  85. public bool IsBeingDragged
  86. {
  87. get { return _isBeingDragged; }
  88. set
  89. {
  90. _isBeingDragged = value;
  91. OnPropertyChanged();
  92. }
  93. }
  94. private bool _isBeingDraggedOver;
  95. public bool IsBeingDraggedOver
  96. {
  97. get { return _isBeingDraggedOver; }
  98. set
  99. {
  100. _isBeingDraggedOver = value;
  101. OnPropertyChanged();
  102. }
  103. }
  104. public Command Remove { get; set; }
  105. public Command Dragged { get; set; }
  106. public Command DraggedOver { get; set; }
  107. public Command DragLeave { get; set; }
  108. public Command Dropped { get; set; }
  109. }

创建页面元素

在Controls目录下创建不同大小的磁贴控件,如下图所示。

在这里插入图片描述

在MainPage中创建CollectionView,用于将磁贴元素以列表形式展示。

  1. <CollectionView Grid.Row="1"
  2. x:Name="MainCollectionView"
  3. ItemsSource="{Binding TileSegments}"
  4. ItemTemplate="{StaticResource TileSegmentDataTemplateSelector}">
  5. <CollectionView.ItemsLayout>
  6. <LinearItemsLayout Orientation="Vertical" />
  7. </CollectionView.ItemsLayout>
  8. </CollectionView>

创建MainPageViewModel,创建绑定服务类集合TileSegments,初始化中添加一些不同颜色,大小的磁贴,并将TileSegementService.Container设置为自己(this)。

不同大小的磁贴通过绑定相应的数据,使用不同的数据模板进行展示。请阅读博文 [MAUI程序设计]界面多态与实现,了解如何实现列表Item的多态。

在这里插入图片描述

在MainPage中创建磁贴片段数据模板选择器(TileSegmentDataTemplateSelector),用于根据磁贴片段的大小选择不同的数据模板。

  1. <DataTemplate x:Key="SmallSegment">
  2. <controls1:SmallSegmentView Margin="0,5"
  3. ControlTemplate="{StaticResource TileSegmentTemplate}">
  4. </controls1:SmallSegmentView>
  5. </DataTemplate>
  6. <DataTemplate x:Key="MediumSegment">
  7. <controls1:MediumSegmentView Margin="0,5"
  8. ControlTemplate="{StaticResource TileSegmentTemplate}">
  9. </controls1:MediumSegmentView>
  10. </DataTemplate>
  11. <DataTemplate x:Key="LargeSegment">
  12. <controls1:LargeSegmentView Margin="0,5"
  13. ControlTemplate="{StaticResource TileSegmentTemplate}">
  14. </controls1:LargeSegmentView>
  15. </DataTemplate>
  16. <controls1:TileSegmentDataTemplateSelector x:Key="TileSegmentDataTemplateSelector"
  17. ResourcesContainer="{x:Reference Main}" />

创建磁贴控件模板TileSegmentTemplate,并在此指定DropGestureRecognizer

  1. <ControlTemplate x:Key="TileSegmentTemplate">
  2. <ContentView>
  3. <StackLayout>
  4. <StackLayout.GestureRecognizers>
  5. <DropGestureRecognizer AllowDrop="True"
  6. DragLeaveCommand="{TemplateBinding BindingContext.DragLeave}"
  7. DragLeaveCommandParameter="{TemplateBinding}"
  8. DragOverCommand="{TemplateBinding BindingContext.DraggedOver}"
  9. DragOverCommandParameter="{TemplateBinding}"
  10. DropCommand="{TemplateBinding BindingContext.Dropped}"
  11. DropCommandParameter="{TemplateBinding}" />
  12. </StackLayout.GestureRecognizers>
  13. </StackLayout>
  14. </ContentView>
  15. </ControlTemplate>

创建磁贴控件外观Layout,<ContentPresenter />处将呈现磁贴片段的内容。在Layout指定DragGestureRecognizer。

  1. <Border x:Name="ContentLayout"
  2. Margin="0">
  3. <Grid>
  4. <Grid.GestureRecognizers>
  5. <DragGestureRecognizer CanDrag="True"
  6. DragStartingCommand="{TemplateBinding BindingContext.Dragged}"
  7. DragStartingCommandParameter="{TemplateBinding}" />
  8. </Grid.GestureRecognizers>
  9. <ContentPresenter />
  10. <Button CornerRadius="100"
  11. HeightRequest="20"
  12. WidthRequest="20"
  13. Padding="0"
  14. BackgroundColor="Red"
  15. TextColor="White"
  16. Command="{TemplateBinding BindingContext.Remove}"
  17. Text="×"
  18. HorizontalOptions="End"
  19. VerticalOptions="Start"></Button>
  20. </Grid>
  21. </Border>

在这里插入图片描述

创建占位控件,用于指示松开手指时,控件将放置的位置区域,在这里绑定DropPlaceHolderItem的高度和宽度。

  1. <Border StrokeThickness="4"
  2. StrokeDashArray="2 2"
  3. StrokeDashOffset="6"
  4. Stroke="black"
  5. HorizontalOptions="Center"
  6. IsVisible="{TemplateBinding BindingContext.IsBeingDraggedOver}">
  7. <Grid HeightRequest="{TemplateBinding BindingContext.DropPlaceHolderItem.Height}"
  8. WidthRequest="{TemplateBinding BindingContext.DropPlaceHolderItem.Width}">
  9. <Label HorizontalTextAlignment="Center"
  10. VerticalOptions="Center"
  11. Text="松开手指将放置条目至此处"></Label>
  12. </Grid>
  13. </Border>

最终效果

在这里插入图片描述

项目地址

Github:maui-samples

关注我,学习更多.NET MAUI开发知识!

原文链接: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

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号