[MAUI]在.NET MAUI中实现可拖拽排序列表
.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接口,定义拖动相关的属性和命令。
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; }
}
创建绑定服务类
创建可拖拽控件的绑定服务类TileSegmentService,继承ObservableObject,并实现IDraggableItem接口。
public class TileSegmentService : ObservableObject, ITileSegmentService
{
...
}
拖拽(Drag)
拖拽开始时,将IsBeingDragged设置为true,通知当前控件正在被拖拽,同时将DraggedItem设置为当前控件。
private void OnDragged(object item)
{
IsBeingDragged=true;
DraggedItem=item;
}
拖拽悬停,经过(DragOver)
拖拽控件悬停在当前控件上方时,将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;
}
}
}
离开控件上方时,IsBeingDraggedOver设置为false
private void OnDragLeave(object item)
{
IsBeingDraggedOver = false;
}
释放(Drop)
拖拽完成时,获取当前正在被拖拽的控件,将其从服务列表中移除,然后将其插入到当前控件的位置,通知当前控件拖拽完成。
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; }
}
创建页面元素
在Controls目录下创建不同大小的磁贴控件,如下图所示。
在MainPage中创建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}" />
创建磁贴控件模板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>
创建磁贴控件外观Layout,<ContentPresenter />
处将呈现磁贴片段的内容。在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>
创建占位控件,用于指示松开手指时,控件将放置的位置区域,在这里绑定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>
最终效果
项目地址
关注我,学习更多.NET MAUI开发知识!
[MAUI]在.NET MAUI中实现可拖拽排序列表的更多相关文章
- jQuery可拖拽排序列表jquery-sortable-lists
jquery-sortable-lists可以通过鼠标进行拖动排列树型菜单,可以定义某个列表元素是否拖动,拖动后回调,点击可以折叠树型结点,可以用来在后台模仿wordpress后台拖动菜单,实现多级菜 ...
- 移动端的拖拽排序在react中实现 了解一下
最近做一个拖拽排序的功能找了好几个有一个步骤简单,结合redux最好不过了,话不多说上代码 第一步: npm install react-draggable-tags --save 第二步 sort. ...
- vue中基于sortablejs与el-upload实现文件上传后拖拽排序
今天做冒烟测试的时候发现商品发布有一个拖拽图片排序功能没做,赶紧加上 之前别的同事基于 vuedraggable 实现过这个功能,我这里自己深度封装了 el-upload ,用这种方式改动很大,而且感 ...
- dragsort html拖拽排序
一.Jquery List DragSort 对于有些页面,如首页的定制,需要进行动态的拖拽排序.由于自己实现比较困难,我们一般会使用一些js插件来实现.dragsort 就是帮助我们完成这一需求.通 ...
- 使用knockout-sortable实现对自定义菜单的拖拽排序
在开始之前,照例,我们先看效果和功能实现. 关于自定义菜单的实现,这里就不多说了,需要了解的请访问:http://www.cnblogs.com/codelove/p/4838766.html 这里需 ...
- RecyclerView拖拽排序和滑动删除实现
效果图 如何实现 那么是如何实现的呢?主要就要使用到ItemTouchHelper ,ItemTouchHelper 一个帮助开发人员处理拖拽和滑动删除的实现类,它能够让你非常容易实现侧滑删除.拖拽的 ...
- jquery sortTable拖拽排序
所有的事件回调函数都有两个参数:event和ui,浏览器自有event对象,和经过封装的ui对象 ui.helper - 表示sortable元素的JQuery对象,通常是当前元素的克隆对象 ...
- ListView列表拖拽排序
ListView列表拖拽排序能够參考Android源代码下的Music播放列表,他是能够拖拽的,源代码在[packages/apps/Music下的TouchInterceptor.java下]. 首 ...
- zTree的拖拽排序
ztree本身是可以支持拖拽的,但是却没有找到明确的支持拖拽的排序,也就是说,在拖拽过程中,需要自定义维护拖拽后的顺序并保存至后台. 在这样一个比较常规的需求情况下,网上也有朋友给出了一些解决方案,比 ...
- RecyclerViewItemTouchHelperDemo【使用ItemTouchHelper进行拖拽排序功能】
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 记录使用ItemTouchHelper对Recyclerview进行拖拽排序功能的实现. 效果图 代码分析 ItemTouchHel ...
随机推荐
- 2021-08-18:扰乱字符串。使用下面描述的算法可以扰乱字符串 s 得到字符串 t :1.如果字符串的长度为 1 ,算法停止。2.如果字符串的长度 > 1 ,执行下述步骤:在一个随机下标处将字符串
2021-08-18:扰乱字符串.使用下面描述的算法可以扰乱字符串 s 得到字符串 t :1.如果字符串的长度为 1 ,算法停止.2.如果字符串的长度 > 1 ,执行下述步骤:在一个随机下标处将 ...
- 【重学C++】01| C++ 如何进行内存资源管理?
文章首发 [重学C++]01| C++ 如何进行内存资源管理? 前言 大家好,我是只讲技术干货的会玩code,今天是[重学C++]的第一讲,我们来学习下C++的内存管理. 与java.golang等自 ...
- 一文读懂面试官都在问的Log4J2漏洞
CVE-2021-44228 漏洞简介 Apache Log4j2是一个基于Java的日志记录工具,当前被广泛应用于业务系统开发,开发者可以利用该工具将程序的输入输出信息进行日志记录. 2021年11 ...
- 计算机网络 传输层协议TCP和UDP
目录 一.传输层协议 二.tcp协议介绍 三.tcp报文格式 四.tcp三次握手 五.tcp四次挥手 六.udp协议介绍 七.常见协议和端口 八.有限状态机 一.传输层协议 传输层协议主要是TCP和U ...
- ESlint配置详解
开发中出现eslint提示代码格式错误,有时候不明白其配置规范,是件很头疼的事情到处找api又是半天:so记录一份配置详情便于开发中翻阅 { // 环境定义了预定义的全局变量. "env&q ...
- 金三银四抢人季,HR 如何 3 招做到效率为王?
春招伊始,面对队伍庞大的校招人群,蜂拥而入的简历,HR 如何才能快速搞定呢?Bug君总结了一下过往招聘季的一些比较流行的环节: 通过线上宣讲,节省出行成本.时间,老板更认可了 现在大多数企业都会在直播 ...
- ChatGPT使用案例,助你快速上手,做事事半功倍
ChatGPT介绍 首先我们来看一下chat-gpt自己的介绍: ChatGPT的发展历程 2015年,OpenAI成立,致力于研究和开发人工智能技术.在成立初期,OpenAI的创始人之一Elon M ...
- CMU15445 (Fall 2020) 数据库系统 Project#4 - Concurrency Control 详解
前言 一个合格的事务处理系统,应该具备四个性质:原子性(atomicity).一致性(consistency).隔离性(isolation)和持久性(durability).隔离性保证了一个活跃的事务 ...
- ElasticSearch的使用和介绍
1.概述 功能 Elasticsearch 是一个分布式的 RESTful 搜索和分析引擎,可用来集中存储您的数据,以便您对形形色色.规模不一的数据进行搜索.索引和分析. 例如: 在电商网站搜索商品 ...
- CF1034D Intervals of Intervals
简要题意 给定 \(n\) 个区间组成的序列,定义它的一个连续段的价值为这个段内所有区间的并覆盖的长度.求价值前 \(k\) 大的段的价值和. 数据范围:\(1\le n\le 3\times 10^ ...