当前位置: 首页 > news >正文

【HarmonyOS】应用开发拖拽功能详解

【HarmonyOS】应用开发拖拽功能详解

一、前言

拖拽交互本质上是一种通过鼠标或手势触屏传递数据的机制,用户可以从一个组件位置拖出数据并将其拖入到另一个组件位置,从而触发相应的响应。

在鸿蒙 中,ArkUI 框架对拖拽功能提供了完整的支持,从基础的单组件拖拽到复杂的多选拖拽、跨应用数据传递,再到自定义动效和悬停检测,形成了一套完善的解决方案。系统级别的支持,让我们对复杂的拖拽功能实现,非常容易就可完成。

二、拖拽事件流程与实现步骤

1、核心流程

因为鸿蒙是面向多设备的操作系统,拖拽流程主要包含手势拖拽鼠标拖拽两种模式,两者在触发条件和交互细节上略有不同,但核心逻辑一致。
在这里插入图片描述
整个拖拽过程:
(1)拖拽操作:长按并滑动触发,释放时结束
对于手势操作,当用户在可拖拽组件上长按超过 500ms 时会触发拖拽,长按 800ms 时系统会执行预览图的浮起动效。

而鼠标拖拽则遵循"即拖即走"模式,当鼠标左键在可拖拽组件上按下并移动超过 1vp 时,即可触发拖拽功能。

(2)拖拽背板:拖动数据时的可视化表示,可自定义
(3)拖拽内容:使用 UDMF 统一数据框架封装,确保数据一致性和安全性
(4)拖出对象:触发拖拽并提供数据的组件
(5)拖入目标:接收并处理拖拽数据的组件

2、回调事件

ArkUI 提供了一系列回调事件,帮助开发者感知拖拽状态并调整系统默认行为:

回调事件说明
onDragStart拖出动作开始时触发,可设置传递数据和自定义背板图
onDragEnter`拖拽点进入组件范围时触发(组件监听了onDrop)
onDragMove拖拽点在组件范围内移动时触发
onDragLeave拖拽点移出组件范围时触发
onDrop用户在组件范围内释放拖拽时触发,需设置拖拽结果
onDragEnd拖拽活动终止时触发,可获取最终结果
onPreDrag拖拽开始前的不同阶段触发,可准备相关数据

3、数据传递与背板定制

拖拽数据通过 UDMF(用户数据管理框架)进行封装,确保跨组件和跨应用的数据一致性。在 onDragStart 回调中,可通过 setData 方法设置传递的统一数据:

onDragStart((event) => {let data: unifiedDataChannel.Image = new unifiedDataChannel.Image();data.imageUri = 'common/pic/img.png';let unifiedData = new unifiedDataChannel.UnifiedData(data);event.setData(unifiedData);
})

三、DEMO 源码

拖拽功能实现

import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData';
import { promptAction } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';@Entry
@Component
struct DragAndDropDemo {@State targetImage: string = '';@State imageWidth: number = 100;@State imageHeight: number = 100;@State imgState: Visibility = Visibility.Visible;@State pixmap: image.PixelMap | undefined = undefined;@BuilderpixelMapBuilder() {Column() {Image($r('app.media.startIcon')).width(120).height(120).backgroundColor(Color.Yellow)}}// 获取UDMF数据,包含重试机制getDataFromUdmfRetry(event: DragEvent, callback: (data: DragEvent) => void) {try {let data: unifiedDataChannel.UnifiedData = event.getData();if (!data) {return false;}let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords();if (!records || records.length <= 0) {return false;}callback(event);return true;} catch (e) {console.log("getData failed, message: " + (e as Error).message);return false;}}getDataFromUdmf(event: DragEvent, callback: (data: DragEvent) => void) {if (this.getDataFromUdmfRetry(event, callback)) {return;}setTimeout(() => {this.getDataFromUdmfRetry(event, callback);}, 1500);}// 生成自定义背板图private getComponentSnapshot(): void {this.getUIContext().getComponentSnapshot().createFromBuilder(() => {this.pixelMapBuilder();}, (error: Error, pixmap: image.PixelMap) => {if (error) {console.log("error: " + JSON.stringify(error));return;}this.pixmap = pixmap;})}// 长按准备阶段处理private preDragChange(preDragStatus: PreDragStatus): void {if (preDragStatus === PreDragStatus.ACTION_DETECTING_STATUS) {this.getComponentSnapshot();}}build() {Row() {Column() {Text('拖动源').fontSize(18).width('100%').height(40).margin(10).backgroundColor('#008888')Row() {Image($r('app.media.app_icon')).width(100).height(100).draggable(true).margin({ left: 15 }).visibility(this.imgState)// 平行手势处理长按冲突.parallelGesture(LongPressGesture().onAction(() => {this.getUIContext().getPromptAction().showToast({ duration: 100, message: '长按手势触发' });})).onDragStart((event) => {let data: unifiedDataChannel.Image = new unifiedDataChannel.Image();data.imageUri = 'common/pic/img.png';let unifiedData = new unifiedDataChannel.UnifiedData(data);event.setData(unifiedData);let dragItemInfo: DragItemInfo = {pixelMap: this.pixmap,extraInfo: "拖拽背板额外信息",};return dragItemInfo;}).onPreDrag((status: PreDragStatus) => {this.preDragChange(status);}).onDragEnd((event) => {if (event.getResult() === DragResult.DRAG_SUCCESSFUL) {this.getUIContext().getPromptAction().showToast({ duration: 100, message: '拖拽成功' });} else if (event.getResult() === DragResult.DRAG_FAILED) {this.getUIContext().getPromptAction().showToast({ duration: 100, message: '拖拽失败' });}})}}Column() {Text('目标区域').fontSize(20).width('100%').height(40).margin(10).backgroundColor('#008888')Row() {Image(this.targetImage).width(this.imageWidth).height(this.imageHeight).draggable(true).margin({ left: 15 }).border({ color: Color.Black, width: 1 }).allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE]).onDragMove((event) => {event.setResult(DragResult.DROP_ENABLED);event.dragBehavior = DragBehavior.MOVE;}).onDrop((dragEvent?: DragEvent) => {this.getDataFromUdmf((dragEvent as DragEvent), (event: DragEvent) => {let records: Array<unifiedDataChannel.UnifiedRecord> = event.getData().getRecords();let rect: Rectangle = event.getPreviewRect();this.imageWidth = Number(rect.width);this.imageHeight = Number(rect.height);this.targetImage = (records[0] as unifiedDataChannel.Image).imageUri;this.imgState = Visibility.None;event.setResult(DragResult.DRAG_SUCCESSFUL);});})}}}.height('100%')}
}
http://www.lryc.cn/news/579753.html

相关文章:

  • MySQL 8.0 OCP 1Z0-908 题目解析(17)
  • 高边驱动 低边驱动
  • IOC容器讲解以及Spring依赖注入最佳实践全解析
  • 【数据结构】哈希——闭散列/开散列模拟实现(C++)
  • 魔术方法__call__
  • Java的SpringAI+Deepseek大模型实战之会话记忆
  • Python入门Day2
  • 网络编程学习路线图
  • Windows 10 2016 长期服务版
  • 7.3实验部分
  • 工程化实践——标准化Eslint、PrettierTS
  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | DragNDrop(拖拽占用组件)
  • Selenium 自动化测试中跳过机器人验证的完整指南:能用
  • 供应链管理:采购与供应链管理中的核心分析工具
  • js代码中的作用域
  • Linux: perf report数据对比,python
  • ArcGISPro应用指南:ArcGISPro制图全流程详解
  • Java综合练习04
  • 优化Facebook广告投放的五大关键策略
  • 机器学习安装使用教程
  • SpringSecurity01
  • win11设置任务栏为顶部
  • 「Java流程控制」循环综合应用
  • oracle锁表,oracle解锁表,oracle用户连接数
  • [自然语言处理]计算语言的熵
  • [自然语言处理]汉语文本分词
  • 百战商店项目学习心得
  • Qt Creator自定义控件开发流程
  • visio画大括号和失败的大模型画图尝试
  • Doris 数据导入性能优化全攻略:深度诊断与全面提速指南