ArkUI 统一拖拽:拖得起来,也要放得明白

举报
蓝瘦的蜕变 发表于 2026/10/24 17:23:18 2026/10/24
【摘要】 今天我们做一个最小拖放 Demo:左边卡片拖起来,右边区域接住它。 拖拽不只是 draggable(true)。用户需要知道什么时候开始拖、什么时候进入目标区域、什么时候放下成功。少了这些反馈,拖拽就会像“东西飘走了”。 参考文档: https://developer.huawei.com/consumer/cn/do

ArkUI 统一拖拽:拖得起来,也要放得明白

今天我们做一个最小拖放 Demo:左边卡片拖起来,右边区域接住它。

拖拽不只是 draggable(true)。用户需要知道什么时候开始拖、什么时候进入目标区域、什么时候放下成功。少了这些反馈,拖拽就会像“东西飘走了”。

参考文档:

  • https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-interaction-development-guide-overview

拖动项要打开 draggable

.draggable(true)
.onDragStart((event: DragEvent, extraParams?: string): DragItemInfo => {
  let info: DragItemInfo = {
    builder: () => {
      this.dragPreviewBuilder()
    },
    extraInfo: 'ArkUI drag demo'
  }
  return info
})

这里用 builder 做拖拽预览,不引入 PixelMap,也不做跨应用数据。注意不要把组件里的 builder 裸传给拖拽系统,拖拽预览会在稍后的系统流程里被调用,直接裸传容易丢掉组件上下文。我们用 @LocalBuilder 加一层箭头函数包装,先把本页内拖放跑通就够了。

放置区域要给反馈

.onDragEnter(() => {
  this.dropActive = true
})
.onDrop(() => {
  this.droppedCount += 1
})

onDragEnter、onDragLeave、onDrop 这些回调不是摆设。它们能让目标区域知道自己现在是不是“可以接住”拖拽项。

你更想看哪种拖拽场景:卡片排序、拖到收藏夹,还是文件拖放?不同场景会影响数据结构和反馈方式。

完整示例代码

文件位置:entry/src/main/ets/pages/arkUI/interaction/ArkUIDragDemo.ets

@Entry
@Component
struct ArkUIDragDemo {
  @State dragText: string = '长按并拖动左侧卡片,放到右侧区域'
  @State dropActive: boolean = false
  @State droppedCount: number = 0

  private showTip(message: string): void {
    this.getUIContext().getPromptAction().showToast({
      message: message,
      duration: 1200,
      bottom: 120
    })
  }

  @LocalBuilder
  dragPreviewBuilder() {
    Row({ space: 8 }) {
      Text('拖拽中')
        .fontSize(14)
        .fontWeight(FontWeight.Bold)
        .fontColor('#FFFFFF')
    }
    .padding({ left: 16, right: 16, top: 10, bottom: 10 })
    .borderRadius(18)
    .backgroundColor('#111827')
  }

  @Builder
  sectionTitle(title: string, desc: string) {
    Column({ space: 6 }) {
      Text(title)
        .fontSize(19)
        .fontWeight(FontWeight.Bold)
        .fontColor('#111827')
      Text(desc)
        .fontSize(13)
        .fontColor('#6B7280')
        .lineHeight(20)
    }
    .width('100%')
    .alignItems(HorizontalAlign.Start)
  }

  build() {
    Scroll() {
      Column({ space: 22 }) {
        Column({ space: 8 }) {
          Text('ArkUI 统一拖拽')
            .fontSize(28)
            .fontWeight(FontWeight.Bold)
            .fontColor('#111827')
          Text('拖得起来,也要放得明白')
            .fontSize(16)
            .fontColor('#4B5563')
          Text('拖拽不是只有 draggable。开始拖、进入目标、移动、离开、放下、结束,每一步都应该有明确反馈。')
            .fontSize(14)
            .fontColor('#6B7280')
            .lineHeight(22)
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)

        this.sectionTitle('拖动左侧卡片', 'Demo 只做本页内拖放反馈,不处理跨应用数据,也不接入 UDMF。')
        Row({ space: 14 }) {
          Column({ space: 10 }) {
            Text('拖动项')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor('#FFFFFF')
            Text('长按后拖到右侧')
              .fontSize(13)
              .fontColor('#DBEAFE')
          }
          .width('45%')
          .height(150)
          .padding(16)
          .borderRadius(18)
          .backgroundColor('#2563EB')
          .justifyContent(FlexAlign.Center)
          .alignItems(HorizontalAlign.Start)
          .draggable(true)
          .onDragStart((event: DragEvent, extraParams?: string): DragItemInfo => {
            this.dragText = 'onDragStart:拖拽开始'
            this.showTip('拖拽开始')
            let info: DragItemInfo = {
              builder: () => {
                this.dragPreviewBuilder()
              },
              extraInfo: 'ArkUI drag demo'
            }
            return info
          })
          .onDragEnd((event: DragEvent, extraParams?: string) => {
            this.dropActive = false
            this.dragText = 'onDragEnd:拖拽流程结束'
            this.showTip('拖拽结束')
          })

          Column({ space: 10 }) {
            Text(this.dropActive ? '释放到这里' : '放置区域')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor(this.dropActive ? '#FFFFFF' : '#0F172A')
            Text(`已放下 ${this.droppedCount} 次`)
              .fontSize(13)
              .fontColor(this.dropActive ? '#DCFCE7' : '#64748B')
          }
          .width('55%')
          .height(150)
          .padding(16)
          .borderRadius(18)
          .backgroundColor(this.dropActive ? '#16A34A' : '#FFFFFF')
          .border({ width: 1, color: this.dropActive ? '#16A34A' : '#CBD5E1' })
          .justifyContent(FlexAlign.Center)
          .alignItems(HorizontalAlign.Start)
          .onDragEnter((event: DragEvent, extraParams?: string) => {
            this.dropActive = true
            this.dragText = 'onDragEnter:进入放置区域'
          })
          .onDragMove((event: DragEvent, extraParams?: string) => {
            this.dragText = 'onDragMove:正在放置区域内移动'
          })
          .onDragLeave((event: DragEvent, extraParams?: string) => {
            this.dropActive = false
            this.dragText = 'onDragLeave:离开放置区域'
          })
          .onDrop((event: DragEvent, extraParams?: string) => {
            this.dropActive = false
            this.droppedCount += 1
            this.dragText = 'onDrop:已经放到目标区域'
            this.showTip('放置成功')
          })
        }
        .width('100%')

        Text(this.dragText)
          .width('100%')
          .fontSize(14)
          .fontColor('#111827')
          .lineHeight(22)
          .padding(16)
          .borderRadius(14)
          .backgroundColor('#FFFFFF')
          .border({ width: 1, color: '#E5E7EB' })
      }
      .width('100%')
      .padding(24)
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F8FAFC')
  }
}
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。