鸿蒙List组件通过拖拽改变排序

效果图和视频

效果是视频

鸿蒙

该组件具备以下核心特性:

  • 长按激活拖拽:长按列表项触发缩放高亮效果,进入可拖拽状态;
  • 实时位置交换:拖拽过程中,当 item 位移超过阈值时,自动与上下相邻项交换位置;
  • 动态视觉反馈:被拖拽项放大显示并提升层级,相邻项随拖拽距离产生缩放动画;
  • 交互开关控制:通过按钮可切换拖拽功能的启用 / 禁用状态,灵活适配业务场景。

代码如下

复制代码
import { curves } from "@kit.ArkUI"

@Entry
@Component
export struct Index {
  @State private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  @State dragItem: number = -1 // 当前拖拽的项目
  @State scaleItem: number = -1 // 当前缩放的项目
  @State neighborItem: number = -1 // 相邻项目
  @State neighborScale: number = -1 // 相邻项目的缩放比例
  private dragRefOffset: number = 0 // 拖拽参考偏移
  @State offsetX: number = 0 // 偏移量
  @State offsetY: number = 0
  private itemIntv: number = 120 // 项目间隔
  @State moveControls: boolean = false // 控制拖拽功能

  scaleSelect(item: number): number {
    if (this.scaleItem == item) {
      return 1.05
    } else if (this.neighborItem == item) {
      return this.neighborScale
    } else {
      return 1
    }
  }

  itemMove(index: number, newIndex: number): void {
    let tmp = this.arr.splice(index, 1)
    this.arr.splice(newIndex, 0, tmp[0])
  }

  build() {
    Stack() {
      Column() {
        Button('是否可以拖动:' + !this.moveControls)
          .width(200)
          .margin(20)
          .onClick(() => {
            this.moveControls = !this.moveControls
          })
        List({ space: 20, initialIndex: 0 }) {
          ForEach(this.arr, (item: number) => {
            ListItem() {
              Text('' + item)
                .width('100%')
                .height(100)
                .fontSize(16)
                .textAlign(TextAlign.Center)
                .borderRadius(10)
                .backgroundColor(0xFFFFFF)// 通过状态变量scaleItem判断是否为组件添加阴影效果
                .shadow(this.scaleItem == item ? {
                  radius: 70,
                  color: '#15000000',
                  offsetX: 0,
                  offsetY: 0
                } : {
                  radius: 0,
                  // 阴影半径为0,相当于没有阴影
                  color: '#15000000',
                  offsetX: 0,
                  offsetY: 0
                })// 设置锐利曲线动画,持续时间为300毫秒
                .animation({ curve: Curve.Sharp, duration: 300 })
            }
            .draggable(this.moveControls)
            .margin({ left: 12, right: 12 })
            .scale({ x: this.scaleSelect(item), y: this.scaleSelect(item) })
            // 增加x轴、y轴缩放效果
            .zIndex(this.dragItem == item ? 1 : 0)
            // 设置组件的堆叠顺序,实现拖拽过程中被拖拽组件覆盖其他组件的效果
            .translate(this.dragItem == item ? { y: this.offsetY } : { y: 0 })
            .gesture(
              // 以下组合手势为顺序识别,当长按手势事件未正常触发时则不会触发拖动手势事件
              GestureGroup(GestureMode.Sequence,
                // 长按手势识别
                LongPressGesture({ repeat: true })
                  .onAction((event?: GestureEvent) => { // 长按手势识别成功回调
                    // 设置显示动画为阻尼曲线,持续时间为300毫秒
                    animateTo({ curve: Curve.Friction, duration: 300 }, () => {
                      this.scaleItem = item
                    })
                  })// 长按手势识别成功,最后一根手指抬起后触发回调
                  .onActionEnd(() => {
                    // 设置显示动画为阻尼曲线,持续时间为300毫秒
                    animateTo({ curve: Curve.Friction, duration: 300 }, () => {
                      this.scaleItem = -1
                    })
                  }),
                // 设置滑动手势事件,任意滑动方向都能够触发事件,触发滑动手势事件的最小滑动距离为0
                PanGesture({ fingers: 1, direction: null, distance: 0 })// 滑动手势识别成功回调
                  .onActionStart(() => {
                    this.dragItem = item
                    this.dragRefOffset = 0
                  })// 滑动手势移动过程中回调
                  .onActionUpdate((event: GestureEvent) => {
                    this.offsetY = event.offsetY - this.dragRefOffset
                    this.neighborItem = -1
                    let index = this.arr.indexOf(item)
                    let curveValue = curves.initCurve(Curve.Sharp)
                    let value: number = 0
                    // 根据位移计算相邻项的缩放
                    if (this.offsetY < 0 && index > 0) {
                      value = curveValue.interpolate(-this.offsetY / this.itemIntv)
                      this.neighborItem = this.arr[index-1]
                      this.neighborScale = 1 - value / 20
                      console.info('neighborScale:' + this.neighborScale.toString())
                    } else if (this.offsetY > 0 && index < this.arr.length - 1) {
                      value = curveValue.interpolate(this.offsetY / this.itemIntv)
                      this.neighborItem = this.arr[index+1]
                      this.neighborScale = 1 - value / 20
                    }
                    // 根据位移交换排序
                    if (this.offsetY > this.itemIntv / 2 && index < this.arr.length - 1) {
                      // 设置显式动画曲线
                      animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
                        this.offsetY -= this.itemIntv
                        this.dragRefOffset += this.itemIntv
                        this.itemMove(index, index + 1)
                      })
                    } else if (this.offsetY < -this.itemIntv / 2 && index > 0) {
                      animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
                        this.offsetY += this.itemIntv
                        this.dragRefOffset -= this.itemIntv
                        this.itemMove(index, index - 1)
                      })
                    }
                  })// 滑动手势识别成功,手指抬起后触发回调
                  .onActionEnd((event: GestureEvent) => {
                    console.info(this.arr.toString())
                    animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
                      this.dragItem = -1
                      this.neighborItem = -1
                    })
                    animateTo({
                      curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150
                    }, () => {
                      this.scaleItem = -1
                    })
                  })
              )// 滑动手势识别成功,接收到触摸取消事件触发回调
                .onCancel(() => {
                  animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
                    this.dragItem = -1
                    this.neighborItem = -1
                  })
                  animateTo({
                    curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150
                  }, () => {
                    this.scaleItem = -1
                  })
                })
            )

            // 设置页面转场时的纵向的平移距离
          }, (item: number) => item.toString())
        }
        .layoutWeight(1)
      }
      .width('100%')
      .height('100%')
      .backgroundColor(0xDCDCDC)
      .padding({ top: 5 })
    }
  }
}
相关推荐
M-Robots echo7 小时前
安全机制架构解析:M-Robots 分层安全体系,筑牢机器人设备运行底座
机器人·ros·鸿蒙·开源社区·m-robots
云端漫步198717 小时前
HarmonyOS 互动卡片实战:快递卡片与睡眠卡片完整开发流程
华为·harmonyos
智塑未来18 小时前
鸿蒙6.1隐私安心加倍:加密分享、星盾防诈、应用锁三重保护,守护到位
华为·harmonyos
byte轻骑兵19 小时前
2026手机远程办公:向日葵、TeamViewer、ToDesk鸿蒙适配+传文件+AI审计功能对比
智能手机·harmonyos·todesk·teamviewer·手机远程办公
贾伟康19 小时前
【知律|06】HarmonyOS ArkTS 法律分类实战:让民法、劳动、消费等入口可维护
harmonyos·arkts·arkui·分类设计·多设备
M-Robots echo1 天前
M-CLAW,面向具身智能的机器人任务编排开发框架
机器人·ros·鸿蒙·开源社区·m-robots
M-Robots echo1 天前
王成录:机器人产业下一个十年,拼的是 “系统级智能化” 而非单点算法堆叠
机器人·ros·鸿蒙·开源社区·m-robots·王成录
梦想不只是梦与想1 天前
鸿蒙 测试工具:DevEco Testing(一)
测试工具·harmonyos·testing
大锅盖11 天前
Web 工单要调用相机,第一步不是打开取景框,而是建立能力门禁
前端·数码相机·harmonyos
M-Robots echo2 天前
M-Robots ROS 兼容适配方案,实现存量机器人项目低成本迁移
机器人·ros·鸿蒙·开源社区·m-robots