鸿蒙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 = 0private 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})}),// 设置滑动手势事件,任意滑动方向都能够触发事件,触发滑动手势事件的最小滑动距离为0PanGesture({ fingers: 1, direction: null, distance: 0 })// 滑动手势识别成功回调.onActionStart(() => {this.dragItem = itemthis.dragRefOffset = 0})// 滑动手势移动过程中回调.onActionUpdate((event: GestureEvent) => {this.offsetY = event.offsetY - this.dragRefOffsetthis.neighborItem = -1let 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 / 20console.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.itemIntvthis.dragRefOffset += this.itemIntvthis.itemMove(index, index + 1)})} else if (this.offsetY < -this.itemIntv / 2 && index > 0) {animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {this.offsetY += this.itemIntvthis.dragRefOffset -= this.itemIntvthis.itemMove(index, index - 1)})}})// 滑动手势识别成功,手指抬起后触发回调.onActionEnd((event: GestureEvent) => {console.info(this.arr.toString())animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {this.dragItem = -1this.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 = -1this.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 })}}
}