
摘要
拖拽功能在用户界面设计中几乎无处不在。无论是移动一个图标、重新排序列表,还是实现拖拽上传图片,拖动交互都能带来更流畅、直觉的操作体验。在 ArkUI 中,我们可以通过 Drag 和 Drop API 快速实现这些功能。本文将从基础讲起,结合实际场景,通过多个可运行 Demo 帮你快速掌握拖拽交互的实现方式。
引言
在 HarmonyOS 的 ArkUI 框架中,开发者不仅可以构建炫酷的动画和响应式布局,还可以非常便捷地实现复杂的交互逻辑。拖放(Drag & Drop)功能就是其中一个非常实用的 UI 交互能力。它常见于:
- 拖动图片或文件上传;
- 拖动列表项重新排序;
- 拖动组件到目标区域以触发对应操作。
接下来我们就通过可运行代码一步步带你玩转 ArkUI 的拖动能力。
拖动功能的基础实现
设置拖动源(DragSource)
我们可以使用 <DragSource> 包裹任意组件,使它成为可以被拖动的源。以下是一个最基础的拖动例子:
            
            
              ts
              
              
            
          
          // DragSourceDemo.ets
import { DragSource } from '@ohos/ui/drag';
@Component
export struct DragSourceDemo {
  @State draggedText: string = 'Drag Me';
  handleDragStart(event: DragEvent) {
    event.dataTransfer.setData('text', this.draggedText);
    console.log('拖动开始,携带数据:', this.draggedText);
  }
  build() {
    Row() {
      DragSource({ onDragStart: this.handleDragStart.bind(this) }) {
        Text(this.draggedText)
          .fontSize(20)
          .padding(12)
          .backgroundColor('#CDEEFF')
          .border({ width: 2, color: '#007ACC' })
      }
    }
  }
}这段代码的重点在于 onDragStart 事件中通过 dataTransfer.setData 将数据绑定到了拖动行为上。
设置拖放目标(DropTarget)
有了拖动源之后,我们还需要设置一个接收区域(目标),这通过 <DropTarget> 实现:
            
            
              ts
              
              
            
          
          // DropTargetDemo.ets
import { DropTarget } from '@ohos/ui/drag';
@Component
export struct DropTargetDemo {
  @State receivedText: string = 'Drop Here';
  handleDrop(event: DragEvent) {
    const data = event.dataTransfer.getData('text');
    if (data) {
      this.receivedText = `接收到:${data}`;
      console.log('接收到拖动内容:', data);
    }
  }
  build() {
    Row() {
      DropTarget({ onDrop: this.handleDrop.bind(this) }) {
        Text(this.receivedText)
          .fontSize(20)
          .padding(20)
          .backgroundColor('#DDEECC')
          .border({ width: 2, color: '#4CAF50' })
      }
    }
  }
}在 onDrop 中,我们通过 getData() 拿到了拖动时设置的数据,并展示出来。
实际场景一:可排序的卡片列表
应用场景
想象一个备忘录 App 中,我们需要对便签卡片进行排序。通过拖拽调整顺序,比上下箭头更符合用户习惯。
可运行代码示例
            
            
              ts
              
              
            
          
          @Component
export struct SortableCardList {
  @State cards: string[] = ['便签A', '便签B', '便签C'];
  @State dragIndex: number = -1;
  handleDragStart(event: DragEvent, index: number) {
    event.dataTransfer.setData('index', index.toString());
    this.dragIndex = index;
  }
  handleDrop(event: DragEvent, dropIndex: number) {
    const draggedIndex = parseInt(event.dataTransfer.getData('index'));
    if (!isNaN(draggedIndex) && draggedIndex !== dropIndex) {
      let newList = [...this.cards];
      const item = newList.splice(draggedIndex, 1)[0];
      newList.splice(dropIndex, 0, item);
      this.cards = newList;
    }
  }
  build() {
    Column() {
      ForEach(this.cards, (card, index) => {
        DropTarget({ onDrop: (e) => this.handleDrop(e, index) }) {
          DragSource({ onDragStart: (e) => this.handleDragStart(e, index) }) {
            Text(card)
              .fontSize(18)
              .padding(16)
              .margin(8)
              .backgroundColor('#FFF8DC')
              .border({ width: 1, color: '#A0522D' })
          }
        }
      }, item => item)
    }
  }
}实际场景二:图片拖入上传区域
应用场景
比如你正在做一个社交 App,让用户可以拖动图片到某个上传区域,自动触发上传操作。
示例代码
            
            
              ts
              
              
            
          
          @Component
export struct ImageUploadDropZone {
  @State uploadStatus: string = '将图片拖拽至此上传';
  handleDrop(event: DragEvent) {
    const imagePath = event.dataTransfer.getData('imagePath');
    if (imagePath) {
      this.uploadStatus = `上传中:${imagePath}`;
      // 模拟上传逻辑
      setTimeout(() => {
        this.uploadStatus = `上传成功:${imagePath}`;
      }, 2000);
    }
  }
  build() {
    DropTarget({ onDrop: this.handleDrop.bind(this) }) {
      Text(this.uploadStatus)
        .fontSize(16)
        .padding(24)
        .backgroundColor('#F0F8FF')
        .border({ width: 2, color: '#4682B4' })
        .width('90%')
        .height(100)
        .align(Alignment.Center)
    }
  }
}在真实项目中,拖拽上传会配合 File API 一起使用,这里做了简化以便理解和演示。
常见问题答疑(QA)
为什么 onDrop 事件没反应?
通常是因为你没有设置 onDragOver 来阻止默认行为。可以给 <DropTarget> 添加:
            
            
              ts
              
              
            
          
          onDragOver={(e) => e.preventDefault()}如何拖动复杂组件,比如图片和文字组合?
完全可以,只需把整个复合组件包在 <DragSource> 中即可,拖动行为会绑定到整个容器上。
拖动数据是否支持对象?
dataTransfer.setData 只支持字符串,若要传对象,可以先序列化为 JSON,再解析回来:
            
            
              ts
              
              
            
          
          event.dataTransfer.setData('text', JSON.stringify({ id: 123, name: '张三' }))总结
通过 DragSource 和 DropTarget,ArkUI 为我们提供了一种高效、清晰的方式实现拖拽交互。从最简单的文本拖动,到列表排序、图片上传等高级场景,都能轻松实现。结合状态管理和组件组合,你甚至可以构建一个完整的拖拽式 UI 编辑器。
希望本文的多个 Demo 和实战场景,能帮助你更快速上手 ArkUI 的拖放系统。如果你有更复杂的场景需求,比如多设备协同拖拽、动画衔接等,也欢迎继续探索 ArkUI 的分布式能力和动画系统。
如果你需要进一步优化拖拽体验,欢迎留言交流!