鸿蒙中的draggable拖拽事件
本文系统介绍了拖拽操作的实现方法,包括基础概念、触发方式、回调事件和开发流程。详细说明了拖拽过程中的核心角色(拖出对象、拖入目标、拖拽背景)和两种触发方式(手势拖拽需长按≥500ms,鼠标拖拽需移动超过1vp)。重点阐述了7种回调事件的作用时机,以及如何通过PreDragStatus枚举控制拖拽准备阶段。文章还提供了拖拽数据封装(UDMF)、角标样式控制、多选拖拽适配(API12+)和自定义落位动
·
本文同步发表于微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新
一、拖拽事件
1. 拖拽操作
-
用户在可响应拖出的组件上长按并滑动触发拖拽行为
-
释放手指或鼠标时,拖拽操作结束
2. 核心角色
| 角色 | 说明 |
|---|---|
| 拖出对象 | 触发拖拽操作并提供数据的组件 |
| 拖入目标 | 接收并处理拖动数据的组件 |
| 拖拽背景(背板) | 拖拽过程中显示的图像化表示,可通过多种方式自定义 |
| 拖拽内容 | 使用 UDMF(统一数据管理框架)的 UnifiedData 封装的数据 |
| 拖拽点 | 鼠标/手指与屏幕接触位置,用于判断是否进入组件范围 |
二、拖拽方式
1. 手势拖拽
-
触发条件:长按 ≥500ms
-
预览动效:长按 800ms 时开始浮起动画
-
组件支持:
-
默认支持拖出的组件(需设置
draggable(true)):-
Search,TextInput,TextArea,RichEditor -
Text,Image,Hyperlink
-
-
其他组件:需设置
onDragStart回调
-
-
注意事项:如果同时使用 Menu 功能,避免在用户操作 800ms 后才控制菜单显示
2. 鼠标拖拽
-
触发条件:鼠标左键按下并移动超过 1vp
-
其他流程:与手势拖拽相似
三、拖拽回调事件
| 回调事件 | 触发时机 | 主要作用 |
|---|---|---|
| onDragStart | 拖拽开始时 | 设置拖拽数据、自定义背板图 |
| onDragEnter | 拖拽点进入组件范围时 | 感知进入状态 |
| onDragMove | 拖拽点在组件范围内移动时 | 可设置 DragResult 影响外观表现 |
| onDragLeave | 拖拽点移出组件范围时 | 感知离开状态(默认在某些情况下不触发) |
| onDrop | 在组件范围内释放拖拽时 | 接收和处理数据,必须显式设置 setResult |
| onDragEnd | 拖拽活动终止时(拖出方触发) | 获取拖拽结果 |
| onPreDrag (API 12+) | 拖拽开始前的不同阶段 | 提前准备数据 |
| onDragSpringLoading (API 20+) | 拖拽对象悬停在组件上时 | 悬停检测,用于自动触发视图跳转 |
四、PreDragStatus 枚举(API 12+)
| 状态 | 触发时机 | 作用 |
|---|---|---|
ACTION_DETECTING_STATUS |
按下 50ms 时 | 拖拽手势启动阶段 |
PREPARING_FOR_DRAG_DETECTION |
按下 350ms 时 | 拖拽准备完成 |
READY_TO_TRIGGER_DRAG_ACTION |
按下 500ms 时 | 可发起拖拽阶段 |
PREVIEW_LIFT_STARTED |
按下 800ms 时 | 拖拽浮起动效开始 |
PREVIEW_LIFT_FINISHED |
浮起动效完全结束时 | 浮起动效结束 |
PREVIEW_LANDING_STARTED |
落回动效发起时 | 落回动效开始 |
PREVIEW_LANDING_FINISHED |
落回动效结束时 | 落回动效结束 |
ACTION_CANCELED_BEFORE_DRAG |
满足条件后未到动效阶段就抬起手指时 | 拖拽被取消 |
五、DragEvent 方法支持情况
| 方法 | onDragStart | onDragEnter | onDragMove | onDragLeave | onDrop | onDragEnd |
|---|---|---|---|---|---|---|
getData() |
- | - | - | - | ✓ | - |
getSummary() |
- | ✓ | ✓ | ✓ | ✓ | - |
getResult() |
- | - | - | - | - | ✓ |
getPreviewRect() |
- | - | - | - | ✓ | - |
getVelocity/X/Y() |
- | ✓ | ✓ | ✓ | ✓ | - |
getWindowX/Y() |
✓ | ✓ | ✓ | ✓ | ✓ | - |
getDisplayX/Y() |
✓ | ✓ | ✓ | ✓ | ✓ | - |
getX/Y() |
✓ | ✓ | ✓ | ✓ | ✓ | - |
getModifierKeyState() |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
startDataLoading() |
- | - | - | - | ✓ | - |
getDisplayId() |
✓ | ✓ | ✓ | ✓ | ✓ | - |
getDragSource() |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
isRemote() |
✓ | ✓ | ✓ | ✓ | ✓ | - |
getGlobalDisplayX/Y() |
✓ | ✓ | ✓ | ✓ | ✓ | - |
behavior |
- | - | - | - | - | ✓ |
六、DragEvent 的 Set 方法支持
| 方法 | onDragStart | onDragEnter | onDragMove | onDragLeave | onDrop |
|---|---|---|---|---|---|
useCustomDropAnimation |
- | - | - | - | ✓ |
setData() |
✓ | - | - | - | - |
setResult() |
✓(可阻止拖拽发起) | ✓(不作为最终结果) | ✓(不作为最终结果) | ✓(不作为最终结果) | ✓(作为最终结果) |
setDataLoadParams() |
✓ | - | - | - | - |
behavior |
- | ✓ | ✓ | ✓ | ✓ |
七、DragResult 枚举
| 结果 | 说明 |
|---|---|
DRAG_SUCCESSFUL |
数据完全由开发者处理,系统不处理 |
DRAG_FAILED |
数据不再由系统继续处理 |
DRAG_CANCELED |
系统也不需要进行数据处理 |
DROP_ENABLED |
组件允许落入(影响角标显示) |
DROP_DISABLED |
组件不允许落入 |
八、通用拖拽事件(以 Image 组件为例)
1. 基础步骤
Image($r('app.media.app_icon'))
.draggable(true) // 1. 使能拖拽
.onDragStart((event) => {
// 2. 设置拖拽数据
let data = new unifiedDataChannel.Image();
data.imageUri = '资源路径';
let unifiedData = new unifiedDataChannel.UnifiedData(data);
event.setData(unifiedData);
// 3. 返回自定义背板图
return {
pixelMap: this.pixmap,
extraInfo: "额外信息"
};
})
2. 如果需要解决长按手势冲突
.parallelGesture(LongPressGesture().onAction(() => {
// 并行手势,不干扰拖拽
}))
3. 提前准备背板图
.onPreDrag((status: PreDragStatus) => {
if (status == PreDragStatus.ACTION_DETECTING_STATUS) {
this.getComponentSnapshot(); // 提前生成截图
}
})
4. 生成截图的方法
private getComponentSnapshot(): void {
this.getUIContext().getComponentSnapshot().createFromBuilder(
() => { this.pixelMapBuilder() },
(error, pixmap) => {
this.pixmap = pixmap;
}
);
}
5. 确保 onDragLeave 触发
// 在 Ability 中设置
uiContext.getDragController().setDragEventStrictReportingEnabled(true);
九、角标样式控制
1. 通过 allowDrop 控制
.allowDrop([
uniformTypeDescriptor.UniformDataType.HYPERLINK,
uniformTypeDescriptor.UniformDataType.PLAIN_TEXT
])
-
符合类型:显示加号角标
-
不符合:显示禁用角标
-
未设置:不显示加号
2. 通过 onDragMove 控制
.onDragMove((event) => {
event.setResult(DragResult.DROP_ENABLED);
event.dragBehavior = DragBehavior.COPY; // 显示加号
// 或 DragBehavior.MOVE; // 不显示加号
})
十、拖拽数据接收与处理
1. onDrop 回调
.onDrop((dragEvent?: DragEvent) => {
this.getDataFromUdmf(dragEvent as DragEvent, (event: DragEvent) => {
// 1. 获取数据
let records = event.getData().getRecords();
// 2. 处理数据
this.targetImage = (records[0] as Image).imageUri;
// 3. 必须显式设置结果
event.setResult(DragResult.DRAG_SUCCESSFUL);
});
})
2. 拖拽结果(拖出方)
.onDragEnd((event) => {
if (event.getResult() === DragResult.DRAG_SUCCESSFUL) {
// 成功处理
} else if (event.getResult() === DragResult.DRAG_FAILED) {
// 失败处理
}
})
十一、多选拖拽适配(API 12+)
1. Grid/List 组件支持
-
支持组件:
GridItem、ListItem -
触发方式:仅支持
onDragStart
2. 使能多选拖拽
.dragPreviewOptions({
isMultiSelectionEnabled: true, // 启用多选
defaultAnimationBeforeLifting: true // 浮起前显示缩小动画
})
3. 选中状态管理
GridItem()
.selectable(true)
.selected(this.isSelectedGrid[idx]) // 设置选中状态
.onClick(() => {
this.isSelectedGrid[idx] = !this.isSelectedGrid[idx]; // 切换选中
})
4. 选中态样式区分
@Styles normalStyles() { .opacity(1.0) }
@Styles selectStyles() { .opacity(0.4) }
.stateStyles({
normal: this.normalStyles,
selected: this.selectStyles
})
5. 数量角标设置
@State numberBadge: number = 0;
.onClick(() => {
this.isSelectedGrid[idx] = !this.isSelectedGrid[idx];
if (this.isSelectedGrid[idx]) {
this.numberBadge++;
} else {
this.numberBadge--;
}
})
.dragPreviewOptions({ numberBadge: this.numberBadge },
{ isMultiSelectionEnabled: true })
6. 建议
-
最大多选数量:500个
-
数据准备策略:选中时通过
addRecord()逐步添加,避免拖拽时集中处理
十二、自定义落位动效(API 18+)
1. 禁用默认动效
.onDrop((dragEvent: DragEvent) => {
dragEvent.useCustomDropAnimation = true; // 禁用系统默认动效
dragEvent.executeDropAnimation(this.customDropAnimation); // 执行自定义动效
})
2. 自定义动画函数
customDropAnimation = () => {
this.getUIContext().animateTo(
{ duration: 1000, curve: Curve.EaseOut, playMode: PlayMode.Normal },
() => {
this.imageWidth = 200; // 改变大小
this.imageHeight = 200;
this.imgState = Visibility.None;
}
);
}
十三、核心开发流程
1. 拖出方
draggable(true)
↓
onPreDrag(可选,提前准备)
↓
onDragStart(设置数据+背板图)
↓
onDragEnd(接收结果)
2. 拖入方
allowDrop(设置接受的数据类型)
↓
onDragEnter/Move/Leave(可选,UI反馈)
↓
onDrop(必须,处理数据+设置结果)
↓
onDragSpringLoading(可选,悬停检测)
3. 多选拖拽
selectable(true) + selected()
↓
dragPreviewOptions(isMultiSelectionEnabled: true)
↓
选中时逐步添加数据
↓
onDragStart(处理多选数据)
更多推荐



所有评论(0)