效果图和视频

效果是视频
鸿蒙
该组件具备以下核心特性:
- 长按激活拖拽:长按列表项触发缩放高亮效果,进入可拖拽状态;
- 实时位置交换:拖拽过程中,当 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 })
}
}
}
