鸿蒙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 })
    }
  }
}
相关推荐
闭着眼睛学算法4 小时前
【双机位A卷】华为OD笔试之【模拟】双机位A-新学校选址【Py/Java/C++/C/JS/Go六种语言】【欧弟算法】全网注释最详细分类最全的华子OD真题题解
java·c语言·javascript·c++·python·算法·华为od
文火冰糖的硅基工坊4 小时前
[创业之路-691]:历史与现实的镜鉴:从三国纷争到华为铁三角的系统性启示
人工智能·科技·华为·重构·架构·创业
大雷神7 小时前
windows系统实操Flutter鸿蒙环境搭建
华为·harmonyos
2503_928411567 小时前
10.9 了解鸿蒙生态
华为·harmonyos
安卓开发者8 小时前
FFRT的核心并发范式与样例概览
harmonyos
程序员潘Sir9 小时前
鸿蒙应用开发从入门到实战(二十二):使用Stack实现层叠布局
harmonyos
我是华为OD~HR~栗栗呀9 小时前
华为OD-23届考研-Java面经
java·c++·后端·python·华为od·华为·面试
深海的鲸同学 luvi10 小时前
【HarmonyOS】原生 Markdown 渲染解决方案 —— @luvi/lv-markdown-in
华为·harmonyos·markdown·原生渲染