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

GeorgeGcs
• 阅读 8

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

一、前言

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

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

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

1、核心流程

因为鸿蒙是面向多设备的操作系统,拖拽流程主要包含手势拖拽鼠标拖拽两种模式,两者在触发条件和交互细节上略有不同,但核心逻辑一致。 【HarmonyOS】应用开发拖拽功能详解 整个拖拽过程: (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;

  @Builder
  pixelMapBuilder() {
    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%')
  }
}
点赞
收藏
评论区
推荐文章
徐小夕 徐小夕
4年前
基于React+Koa实现一个h5页面可视化编辑器
前言前段时间笔者一直忙于数据可视化方面的工作,比如如何实现拖拽式生成可视化大屏,如何定制可视化图表交互和数据导入方案等,这块需求在B端企业中应用非常大,所以非常有探索价值。本篇文章并非和数据可视化相关,而是通过抽象技术底层,将其应用于H5页面可视化搭建上,通过技术的手段实现拖拽式生成H5页面。这块也有非常多的应用场景,比如我们需要开发一个移动端网站,一
Alex799 Alex799
4年前
Vue进阶(幺柒幺):前端用户体验提升(五)Flex实现弹性布局
需求背景在实现组件拖拽设计过程中,发现组件样式中设置了display:flex属性信息,导致组件生成后无法实现拖拽效果,网上查阅资料后发现Flex布局大有天地。Flex来源
飞速企业级低代码 | 低代码市场火热:是 IT 革命还是高级外包
​低代码,一种快速开发应用的软件,将通用、可重复利用的代码形成组件化的模块,通过图形化的界面来拖拽组件并形成应用。低代码能够实现只写少量代码或不写代码,类似用“乐高积木”的方式来开发。在国外有很多著名的低代码成功案例。Outsystems帮助施耐德电气在20个月内推出了60款应用程序,开发过程加速了两倍,仅在第一年就节省了650人天的工作量;在
taskbuilder taskbuilder
5个月前
TaskBuilder前端页面UI界面设计
TaskBuilder前端页面UI界面设计为了方便大家快速设计前端页面的UI界面,TaskBuilder提供了可视化的UI界面设计器,在该设计器内,可以从左侧的组件库中拖拽组件到页面上实现组件的添加,TaskBuilder提供了几十种丰富的UI组件,分为容
GeorgeGcs GeorgeGcs
1星期前
【HarmonyOS 5】桌面快捷方式功能实现详解
鸿蒙开发能力HarmonyOSSDK应用服务鸿蒙金融类应用(金融理财一、前言在移动应用开发中,如何让用户快速触达核心功能,是目前很常见的功能之一。鸿蒙系统提供的桌面快捷方式(Shortcuts)功能,允许开发者为应用内常用功能创建直达入口,用户通过长按应用
GeorgeGcs GeorgeGcs
1星期前
【HarmonyOS 5】如何开启DevEco Studio热更新调试应用模式
鸿蒙开发能力HarmonyOSSDK应用服务鸿蒙金融类应用(金融理财一、AttributeModifier和AttributeUpdater的定义和作用1.AttributeModifier是ArkUI组件的动态属性,提供属性设置功能。开发者可使用attr
GeorgeGcs GeorgeGcs
1星期前
【HarmonyOS 5】鸿蒙Web组件和内嵌网页双向通信DEMO示例
鸿蒙开发能力HarmonyOSSDK应用服务鸿蒙金融类应用(金融理财一、前言在ArkUI开发中,Web组件(Web)允许开发者在应用内嵌入网页,实现混合开发场景。本文将通过完整DEMO,详解如何通过WebviewController实现ArkUI与内嵌网页
GeorgeGcs GeorgeGcs
6小时前
【HarmonyOS】Web 组件的 PDF 文档预览功能详解
【HarmonyOS】Web组件的PDF文档预览功能详解一、前言应用开发中,PDF文档预览是一项常见需求。虽然官方提供了预览组件,但是在H5业务场景下,如何加载PDF呢?此时就需要Web组件提供了便捷的PDF预览能力。目前官方的ArkWeb,支持加载网络、
商业开源MES+源码+送可拖拽式数据大屏
商业开源MES源码送可拖拽式数据大屏开发学习的好机会
taskbuilder taskbuilder
7个月前
TaskBuilder功能特性
1、基础功能组件化TaskBuilder将常用的功能封装成了组件,包括前端UI组件、后台业务操作等,开发业务功能时,可以像搭积木一样,通过鼠标拖拽就能快速实现前端界面设计和后台功能开发。tfp前端UI组件组件整体结构2功能设计可视化使用TaskBuilde
GeorgeGcs
GeorgeGcs
Lv1
男 · 金融头部企业 · 鸿蒙应用架构师
HarmonyOS认证创作先锋,华为HDE专家,鸿蒙讲师,作者。目前任职鸿蒙应用架构师。 历经腾讯,宝马,研究所,金融。 待过私企,外企,央企。 深耕大应用开发领域十年。 AAE,Harmony(OpenHarmony\HarmonyOS),MAE(Android\IOS),FE(H5\Vue\RN)。
文章
64
粉丝
1
获赞
2