- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
@ 。
其是在松开手指之后才向列表提交条目位置变更的命令。今天我们换一个写法,将拖拽条目放置在另一个条目上方时,即可将条目位置变更。即实时拖拽排序.
使用.NET MAU实现跨平台支持,本项目可运行于Android、iOS平台.
新建.NET MAUI项目,命名Tile 。
本章的实例中使用网格布局的CollectionView控件作为Tile的容器.
CollectionView 的其他布局方式请参考官方文档 指定 CollectionView 布局 。
创建 GridTilesPage.xaml 。
在页面中创建CollectionView, 。
<CollectionView Grid.Row="1"
x:Name="MainCollectionView"
ItemsSource="{Binding TileSegments}">
<CollectionView.ItemTemplate>
<DataTemplate>
<ContentView HeightRequest="110" WidthRequest="110" HorizontalOptions="Center" VerticalOptions="Center">
<StackLayout>
<StackLayout.GestureRecognizers>
<DropGestureRecognizer AllowDrop="True"
DragLeaveCommand="{Binding DragLeave}"
DragLeaveCommandParameter="{Binding}"
DragOverCommand="{Binding DraggedOver}"
DragOverCommandParameter="{Binding}"
DropCommand="{Binding Dropped}"
DropCommandParameter="{Binding}" />
</StackLayout.GestureRecognizers>
<Border x:Name="ContentLayout"
StrokeThickness="0"
Margin="0">
<Grid>
<Grid.GestureRecognizers>
<DragGestureRecognizer CanDrag="True"
DragStartingCommand="{Binding Dragged}"
DragStartingCommandParameter="{Binding}" />
</Grid.GestureRecognizers>
<controls1:TileSegmentView HeightRequest="100"
WidthRequest="100"
Margin="5,5">
</controls1:TileSegmentView>
<Button CornerRadius="100"
HeightRequest="20"
WidthRequest="20"
Padding="0"
Margin="2,2"
BackgroundColor="Red"
TextColor="White"
Command="{Binding Remove}"
Text="×"
HorizontalOptions="End"
VerticalOptions="Start"></Button>
</Grid>
</Border>
</StackLayout>
</ContentView>
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="3" />
</CollectionView.ItemsLayout>
</CollectionView>
呈现效果如下:
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; }
}
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; }
}
创建GridTilesPageViewModel,创建绑定服务类集合TileSegments.
private ObservableCollection<ITileSegmentService> _tileSegments;
public ObservableCollection<ITileSegmentService> TileSegments
{
get { return _tileSegments; }
set
{
_tileSegments = value;
OnPropertyChanged();
}
}
构造函数中初始化一些不同颜色的磁贴,并将TileSegementService.Container设置为自己(this).
public GridTilesPageViewModel()
{
TileSegments = new ObservableCollection<ITileSegmentService>();
CreateSegmentAction("TileSegment", "App1", "Some description here", Colors.LightPink);
CreateSegmentAction("TileSegment", "App2", "Some description here", Colors.LightGreen);
...
}
private ITileSegmentService CreateTileSegmentService(object obj, string title, string desc, Color color)
{
var type = obj as string;
var tileSegment = new TileSegment()
{
Title = title,
Type = type,
Desc = desc,
Icon = "dotnet_bot.svg",
Color = color,
};
var newModel = new GridTileSegmentService(tileSegment);
if (newModel != null)
{
newModel.Container = this;
}
return newModel;
}
创建可拖拽控件的绑定服务类GridTileSegmentService,继承ObservableObject,并实现IDraggableItem接口.
创建ICommand属性:Dragged, DraggedOver, DragLeave, Dropped.
订阅PropertyChanged事件以便在属性更改时触发相关操作 。
public class GridTileSegmentService : ObservableObject, ITileSegmentService
{
public GridTileSegmentService(TileSegment tileSegment)
{
TileSegment = tileSegment;
Dragged = new Command(OnDragged);
DraggedOver = new Command(OnDraggedOver);
DragLeave = new Command(OnDragLeave);
Dropped = new Command(i => OnDropped(i));
this.PropertyChanged+=GridTileSegmentService_PropertyChanged;
}
...
}
拖拽开始时,将IsBeingDragged设置为true,通知当前控件正在被拖拽,同时将DraggedItem设置为当前控件.
private void OnDragged(object item)
{
IsBeingDragged=true;
DraggedItem=item;
}
拖拽控件悬停在当前控件上方时,将IsBeingDraggedOver设置为true,通知当前控件正在有拖拽控件悬停在其上方,同时在服务列表中寻找当前正在被拖拽的服务,将DropPlaceHolderItem设置为当前控件.
private void OnDraggedOver(object item)
{
if (!IsBeingDragged && item!=null)
{
var itemToMove = Container.TileSegments.First(i => i.IsBeingDragged);
if (itemToMove.DraggedItem!=null)
{
DropPlaceHolderItem=itemToMove.DraggedItem;
}
IsBeingDraggedOver=true;
}
}
离开控件上方时,IsBeingDraggedOver设置为false 。
private void OnDragLeave(object item)
{
IsBeingDraggedOver = false;
DropPlaceHolderItem = null;
}
通过订阅PropertyChanged, 在GridTileSegmentService_PropertyChanged方法中响应IsBeingDraggedOver属性的值变更.
当IsBeingDraggedOver为True时代表有拖拽中控件悬停在其上方,DropPlaceHolderItem即为悬停在其上方的控件对象.
此时我们应该将悬停在其上方的控件对象插入到自身的前方,通过获取两者在集合的角标并调用 Move() 方法.
private void GridTileSegmentService_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName==nameof(this.IsBeingDraggedOver))
{
if (this.IsBeingDraggedOver && DropPlaceHolderItem!=null)
{
var newIndex = Container.TileSegments.IndexOf(this);
var oldIndex = Container.TileSegments.IndexOf(DropPlaceHolderItem as ITileSegmentService);
Container.TileSegments.Move(oldIndex, newIndex);
}
}
}
效果如下:
拖拽完成时,获取当前正在被拖拽的控件,将其从服务列表中移除,然后将其插入到当前控件的位置,通知当前控件拖拽完成.
private void OnDropped(object item)
{
var itemToMove = Container.TileSegments.First(i => i.IsBeingDragged);
if (itemToMove == null)
return;
itemToMove.IsBeingDragged = false;
IsBeingDraggedOver = false;
DraggedItem=null;
DropPlaceHolderItem = null;
}
完整的TileSegmentService代码如下:
public class GridTileSegmentService : ObservableObject, ITileSegmentService
{
public GridTileSegmentService(
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));
this.PropertyChanged+=GridTileSegmentService_PropertyChanged;
}
private void GridTileSegmentService_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName==nameof(this.IsBeingDraggedOver))
{
if (this.IsBeingDraggedOver && DropPlaceHolderItem!=null)
{
var newIndex = Container.TileSegments.IndexOf(this);
var oldIndex = Container.TileSegments.IndexOf(DropPlaceHolderItem as ITileSegmentService);
Container.TileSegments.Move(oldIndex, newIndex);
}
}
}
private void OnDragged(object item)
{
IsBeingDragged=true;
DraggedItem=item;
}
private void OnDraggedOver(object item)
{
if (!IsBeingDragged && item!=null)
{
var itemToMove = Container.TileSegments.First(i => i.IsBeingDragged);
if (itemToMove.DraggedItem!=null)
{
DropPlaceHolderItem=itemToMove.DraggedItem;
}
IsBeingDraggedOver=true;
}
}
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;
DropPlaceHolderItem = null;
}
private void OnDropped(object item)
{
var itemToMove = Container.TileSegments.First(i => i.IsBeingDragged);
if (itemToMove == null)
return;
itemToMove.IsBeingDragged = false;
IsBeingDraggedOver = false;
DraggedItem=null;
DropPlaceHolderItem = 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
{
if (value!=_isBeingDraggedOver)
{
_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; }
}
运行程序,此时我们可以看到拖拽控件悬停在其它控件上方时,其它控件会自动调整位置.
在特定平台的列表控件中更新项目集合时,引发的动画效果会导致列表中的控件位置错乱.
当以比较快的速度,拖拽Tile经过较多的位置时,后面的Tile会短暂地替代原先的位置,导致拖拽中的Tile不在期望的Tile上方,而拖拽中的Tile与错误的Tile产生了交叠从而触发DraggedOver事件,导致错乱.
在某些机型上甚至会引发错乱的持续循环 。
一个办法是禁用动画,如在iOS中配置 。
listView.On<iOS>().SetRowAnimationsEnabled(false);
动效问题最终要解决。由于快速拖拽Tile经过较多的位置频繁触发Move操作,通过限制事件的触发频率,引入限流(Throttle)和防抖(Debounce)机制可以有效地解决这个问题。限流和防抖的作用如下图:
代码引用自 ThrottleDebounce 。
在GridTileSegmentService中创建静态限流器对象变量throttledAction。以及全局锁对象throttledLocker.
public static RateLimitedAction throttledAction = Debouncer.Debounce(null, TimeSpan.FromMilliseconds(500), leading: false, trailing: true);
public static object throttledLocker = new object();
改写GridTileSegmentService_PropertyChanged如下
private void GridTileSegmentService_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName==nameof(this.IsBeingDraggedOver))
{
if (this.IsBeingDraggedOver && DropPlaceHolderItem!=null)
{
lock (throttledLocker)
{
var newIndex = Container.TileSegments.IndexOf(this);
var oldIndex = Container.TileSegments.IndexOf(DropPlaceHolderItem as ITileSegmentService);
var originalAction = () =>
{
Container.TileSegments.Move(oldIndex, newIndex);
};
throttledAction.Update(originalAction);
throttledAction.Invoke();
}
}
}
}
此时,在500毫秒内,只会执行一次Move操作。问题解决! 。
因为有500毫秒的延迟,Tile响应上感觉没有那么“灵动”,这算是一种牺牲。在不同的平台上可以调整这个时间以达到一种平衡,不知道屏幕前的你有没有更好的方式解决呢?
Github:maui-samples 。
最后此篇关于[MAUI]实现动态拖拽排序网格的文章就讲到这里了,如果你想了解更多关于[MAUI]实现动态拖拽排序网格的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我们如何才能使 div 从这里移动到那里。我尝试使用 JQuery 拖放和可排序之类的东西,但我的要求没有得到满足。 我的页面上有很多 DIV,我需要将 DIV_1 拖放到 DIV_2 上,然后应该调
简单表格排序 可以双击编辑 自定义编辑后的 规则 可拖动列进行列替换 可推动边框进行列宽度的缩放 复制代码 代码如下: &n
我有一个元素,我想用鼠标移动它。 var troll = document.getElementById('troll'); troll.addEventListener('dragover', (e
我的问题是如何拖放形状,但要克隆可拖动形状,然后将该克隆拖动到可放置形状。 我是 Konva 的新手。在查看文档和示例时,我可以找到如何拖放形状。 我找到了对形状克隆的引用,但我不确定如何执行此操作。
我正在寻找一个 Java UI 设计器,允许我以 float 模式将控件直接拖放到设计表面(没有 SWT 附带的南北等麻烦)。有没有这样的工具? 另外,我只对提供试用版的工具感兴趣。 编辑:我只对允许
@ 目录 Excel上传和图片视频上传 Excel上传 页面中的使用 图片和视频
当放置/放置元素(通过从一个 DIV 拖动到另一个 DIV),然后删除放置的 DIV 中的一个元素时,其中一些元素会更改位置。 这是一个测试场景:http://jsfiddle.net/TcYHW/8
我正在努力做到这一点,以便用户可以将图标从 Web 浏览器拖到他们的桌面,然后创建一个文本文件。我已经了解了内容部分,但我不知道如何设置文件名。我试过改变 dataTransfer.files 但那是
我有一个类似于下面的代码: var dragme = d3.drag() .on("start", function (d) { var variable
前言: 今天一早起床,就一直太阳穴疼,吃了四片去痛片已经无效,真的是疼的直恶心。 如果说学习或者写文章,能够缓解头疼的话,那我想说,我还能坚持一会..... 很久没更新这系列的文章
我是一名优秀的程序员,十分优秀!