本文同步发表于微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

一、拖拽事件

1. 拖拽操作

  • 用户在可响应拖出的组件上长按并滑动触发拖拽行为

  • 释放手指或鼠标时,拖拽操作结束

2. 核心角色

角色 说明
拖出对象 触发拖拽操作并提供数据的组件
拖入目标 接收并处理拖动数据的组件
拖拽背景(背板) 拖拽过程中显示的图像化表示,可通过多种方式自定义
拖拽内容 使用 UDMF(统一数据管理框架)的 UnifiedData 封装的数据
拖拽点 鼠标/手指与屏幕接触位置,用于判断是否进入组件范围

二、拖拽方式

1. 手势拖拽

  • 触发条件:长按 ≥500ms

  • 预览动效:长按 800ms 时开始浮起动画

  • 组件支持

    • 默认支持拖出的组件(需设置 draggable(true)):

      • SearchTextInputTextAreaRichEditor

      • TextImageHyperlink

    • 其他组件:需设置 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 组件支持

  • 支持组件GridItemListItem

  • 触发方式:仅支持 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(处理多选数据)

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐