|
|
@@ -0,0 +1,348 @@
|
|
|
+import curves from '@ohos.curves'
|
|
|
+import { util } from '@kit.ArkTS'
|
|
|
+import { YTLog } from '../../../../../Index'
|
|
|
+
|
|
|
+@Component
|
|
|
+export struct PanGrid {
|
|
|
+ /**
|
|
|
+ * @description 需要渲染的数组
|
|
|
+ * @type extends BasicModel
|
|
|
+ */
|
|
|
+ @Prop @Require @Watch('changeList') displayArr: ESObject[]
|
|
|
+ /**
|
|
|
+ * @description 需要传入渲染的结构
|
|
|
+ */
|
|
|
+ @BuilderParam @Require gridItem: (_: ESObject, index: number) => void
|
|
|
+ /**
|
|
|
+ * @description grid的列数 两列示例: '1fr 1fr' 不支持动态传入
|
|
|
+ */
|
|
|
+ @Require columnsTemplate: string
|
|
|
+ sortOrder: string = 'sortOrder'
|
|
|
+ //完成
|
|
|
+ @Require onFinishCallBack: (arr: ESObject[]) => void
|
|
|
+ @State private dragItemId: number | string = -1
|
|
|
+ @State private scaleItemId: number | string = -1
|
|
|
+ @State private offsetX: number = 0
|
|
|
+ @State private offsetY: number = 0
|
|
|
+ private columnsCount: number = 0
|
|
|
+ private dragRefOffsetx: number = 0
|
|
|
+ private dragRefOffsety: number = 0
|
|
|
+ private FIX_VP_X: number = 108
|
|
|
+ private FIX_VP_Y: number = 120
|
|
|
+ private firstGetArea: boolean = true
|
|
|
+ private isMove: boolean = false
|
|
|
+ //用户传入的数组中是否有id
|
|
|
+ private scroller: Scroller = new Scroller()
|
|
|
+ //使用回调获取Scroller空值grid行为
|
|
|
+ onPanGridAppear = (_: Scroller) => {
|
|
|
+ }
|
|
|
+
|
|
|
+ aboutToAppear() {
|
|
|
+ this.columnsCount = this.columnsTemplate.split(' ').length
|
|
|
+
|
|
|
+
|
|
|
+ this.onPanGridAppear(this.scroller)
|
|
|
+ }
|
|
|
+
|
|
|
+ changeList() {
|
|
|
+ this.displayArr.forEach((item: ESObject) => {
|
|
|
+ if (item.id == undefined) {
|
|
|
+ item.id = util.generateRandomUUID()
|
|
|
+ item.haveId = false
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (this.isMove) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // this.resetStates()
|
|
|
+ }
|
|
|
+
|
|
|
+ build() {
|
|
|
+ Column() {
|
|
|
+ Grid(this.scroller) {
|
|
|
+ ForEach(this.displayArr, (item: ESObject, index: number) => {
|
|
|
+ GridItem() {
|
|
|
+ this.gridItem(item, index)
|
|
|
+ }
|
|
|
+ .backgroundColor(Color.Transparent)
|
|
|
+ .shadow(this.dragItemId == item.id ? {
|
|
|
+ radius: 20, // 模糊半径(对应 20dp)
|
|
|
+ color: '#0F000000', // 阴影颜色
|
|
|
+ offsetX: 0, // 水平偏移
|
|
|
+ offsetY: 0 // 垂直偏移
|
|
|
+ } : undefined)
|
|
|
+ // .borderRadius(16)
|
|
|
+ .onAreaChange(this.firstGetArea ? (_, newArea) => {
|
|
|
+ this.FIX_VP_X = newArea.width as number
|
|
|
+ this.FIX_VP_Y = newArea.height as number
|
|
|
+
|
|
|
+ } : undefined)
|
|
|
+ // 指定固定GridItem不响应事件
|
|
|
+ // .hitTestBehavior(this.isDraggable(this.displayArr.indexOf(item)) ? HitTestMode.Default : HitTestMode.None)
|
|
|
+ // .scale({ x: this.scaleItemId == item.id ? 1.05 : 1, y: this.scaleItemId == item.id ? 1.05 : 1 })
|
|
|
+ .zIndex(this.dragItemId == item.id ? 1 : 0)
|
|
|
+ .translate(this.dragItemId == item.id ? { x: this.offsetX, y: this.offsetY } : { x: 0, y: 0 })
|
|
|
+ .gesture(
|
|
|
+ // 以下组合手势为顺序识别,当长按手势事件未正常触发时则不会触发拖动手势事件
|
|
|
+ GestureGroup(GestureMode.Sequence,
|
|
|
+ LongPressGesture({ repeat: true })
|
|
|
+ .onAction((event?: GestureEvent) => {
|
|
|
+ this.getUIContext()?.animateTo({ curve: Curve.Friction, duration: 300 }, () => {
|
|
|
+ this.scaleItemId = item.id
|
|
|
+ })
|
|
|
+ })
|
|
|
+ .onActionEnd(() => {
|
|
|
+ this.getUIContext()?.animateTo({ curve: Curve.Friction, duration: 300 }, () => {
|
|
|
+ this.scaleItemId = -1
|
|
|
+ })
|
|
|
+ }),
|
|
|
+ PanGesture({ fingers: 1, direction: null, distance: 0 })
|
|
|
+ .onActionStart(() => {
|
|
|
+ this.isMove = true
|
|
|
+ this.dragItemId = item.id
|
|
|
+ this.dragRefOffsetx = 0
|
|
|
+ this.dragRefOffsety = 0
|
|
|
+ })
|
|
|
+ .onActionUpdate((event: GestureEvent) => {
|
|
|
+ this.offsetY = event.offsetY - this.dragRefOffsety
|
|
|
+ this.offsetX = event.offsetX - this.dragRefOffsetx
|
|
|
+ this.getUIContext()?.animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
|
|
|
+ let index = this.displayArr.findIndex((item: ESObject) => item.id == this.dragItemId)
|
|
|
+ if (this.offsetY >= this.FIX_VP_Y / 2 && (this.offsetX <= 44 && this.offsetX >= -44) &&
|
|
|
+ !this.isLastRow(index)) {
|
|
|
+ //向下滑
|
|
|
+ this.down(index)
|
|
|
+ } else if (this.offsetY <= -this.FIX_VP_Y / 2 && (this.offsetX <= 44 && this.offsetX >= -44) &&
|
|
|
+ !this.isFirstRow(index)) {
|
|
|
+ //向上滑
|
|
|
+ this.up(index)
|
|
|
+ } else if (this.offsetX >= this.FIX_VP_X / 2 && (this.offsetY <= 50 && this.offsetY >= -50) &&
|
|
|
+ !this.isLastColumn(index)) {
|
|
|
+ //向右滑
|
|
|
+ this.right(index)
|
|
|
+ } else if (this.offsetX <= -this.FIX_VP_X / 2 && (this.offsetY <= 50 && this.offsetY >= -50) &&
|
|
|
+ !this.isFirstColumn(index)) {
|
|
|
+ //向左滑
|
|
|
+ this.left(index)
|
|
|
+ } else if (this.offsetX >= this.FIX_VP_X / 2 && this.offsetY >= this.FIX_VP_Y / 2 &&
|
|
|
+ !this.isLastRow(index) && !this.isLastColumn(index)) {
|
|
|
+ //向右下滑
|
|
|
+ this.lowerRight(index)
|
|
|
+ } else if (this.offsetX >= this.FIX_VP_X / 2 && this.offsetY <= -this.FIX_VP_Y / 2 &&
|
|
|
+ !this.isFirstRow(index) && !this.isLastColumn(index)) {
|
|
|
+ //向右上滑
|
|
|
+ this.upperRight(index)
|
|
|
+ } else if (this.offsetX <= -this.FIX_VP_X / 2 && this.offsetY >= this.FIX_VP_Y / 2 &&
|
|
|
+ !this.isLastRow(index) && !this.isFirstColumn(index)) {
|
|
|
+ //向左下滑
|
|
|
+ this.lowerLeft(index)
|
|
|
+ } else if (this.offsetX <= -this.FIX_VP_X / 2 && this.offsetY <= -this.FIX_VP_Y / 2 &&
|
|
|
+ !this.isFirstRow(index) && !this.isFirstColumn(index)) {
|
|
|
+ //向左上滑
|
|
|
+ this.upperLeft(index)
|
|
|
+ } else if (this.offsetX >= this.FIX_VP_X / 2 && this.offsetY >= this.FIX_VP_Y / 2 &&
|
|
|
+ this.isLastRow(index) && this.isFirstColumn(index)) {
|
|
|
+ //向右下滑(右下角为空)
|
|
|
+ this.down(index)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ })
|
|
|
+ .onActionEnd(() => {
|
|
|
+ this.getUIContext()?.animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
|
|
|
+ this.dragItemId = -1
|
|
|
+ })
|
|
|
+ this.getUIContext()?.animateTo({
|
|
|
+ curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150
|
|
|
+ }, () => {
|
|
|
+ this.scaleItemId = -1
|
|
|
+ })
|
|
|
+ // 更新sortOrder
|
|
|
+ this.updateSortOrder()
|
|
|
+ this.isMove = false
|
|
|
+ //返回排序后的数组
|
|
|
+ this.onFinishCallBack((() => {
|
|
|
+ YTLog.info(this.displayArr, '删除id')
|
|
|
+ this.displayArr.forEach((item: ESObject) => {
|
|
|
+ //排除undefined的情况
|
|
|
+ if (item.haveId == false) {
|
|
|
+ item.id = undefined
|
|
|
+ }
|
|
|
+ item.haveId = undefined
|
|
|
+ })
|
|
|
+ YTLog.info(this.displayArr, '删除id')
|
|
|
+ return this.displayArr
|
|
|
+ })())
|
|
|
+
|
|
|
+ })
|
|
|
+ )
|
|
|
+ .onCancel(() => {
|
|
|
+ this.getUIContext()?.animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
|
|
|
+ this.dragItemId = -1
|
|
|
+ })
|
|
|
+ this.getUIContext()?.animateTo({
|
|
|
+ curve: curves.interpolatingSpring(14, 1, 170, 17)
|
|
|
+ }, () => {
|
|
|
+ this.scaleItemId = -1
|
|
|
+ })
|
|
|
+ })
|
|
|
+ )
|
|
|
+ }, (item: ESObject, _: number) => {
|
|
|
+ // 使用更稳定的key,优先使用id,其次使用sortOrder,最后使用JSON.stringify
|
|
|
+ return JSON.stringify(item);
|
|
|
+ })
|
|
|
+ }
|
|
|
+ .width('100%%')
|
|
|
+ .editMode(true)
|
|
|
+ .scrollBar(BarState.Off)
|
|
|
+ .columnsTemplate(this.columnsTemplate)
|
|
|
+ .rowsGap(8)
|
|
|
+
|
|
|
+ }
|
|
|
+ .width('100%')
|
|
|
+ .padding({ top: 5 })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重置所有状态
|
|
|
+ private resetStates(): void {
|
|
|
+ this.dragItemId = -1
|
|
|
+ this.scaleItemId = -1
|
|
|
+ this.offsetX = 0
|
|
|
+ this.offsetY = 0
|
|
|
+ this.dragRefOffsetx = 0
|
|
|
+ this.dragRefOffsety = 0
|
|
|
+ }
|
|
|
+
|
|
|
+ private itemMove(index: number, newIndex: number): void {
|
|
|
+ if (!this.isDraggable(newIndex)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let tmp: ESObject[] = this.displayArr.splice(index, 1)
|
|
|
+ this.displayArr.splice(newIndex, 0, tmp[0])
|
|
|
+ }
|
|
|
+
|
|
|
+ //向下滑
|
|
|
+ private down(index: number): void {
|
|
|
+ // 指定固定GridItem不响应事件
|
|
|
+ if (!this.isDraggable(index + this.columnsCount)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.offsetY -= this.FIX_VP_Y
|
|
|
+ this.dragRefOffsety += this.FIX_VP_Y
|
|
|
+ this.itemMove(index, index + this.columnsCount)
|
|
|
+ }
|
|
|
+
|
|
|
+ //向上滑
|
|
|
+ private up(index: number): void {
|
|
|
+ if (!this.isDraggable(index - this.columnsCount)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.offsetY += this.FIX_VP_Y
|
|
|
+ this.dragRefOffsety -= this.FIX_VP_Y
|
|
|
+ this.itemMove(index, index - this.columnsCount)
|
|
|
+ }
|
|
|
+
|
|
|
+ //向左滑
|
|
|
+ private left(index: number): void {
|
|
|
+ if (!this.isDraggable(index - 1)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.offsetX += this.FIX_VP_X
|
|
|
+ this.dragRefOffsetx -= this.FIX_VP_X
|
|
|
+ this.itemMove(index, index - 1)
|
|
|
+ }
|
|
|
+
|
|
|
+ //向右滑
|
|
|
+ private right(index: number): void {
|
|
|
+ if (!this.isDraggable(index + 1)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.offsetX -= this.FIX_VP_X
|
|
|
+ this.dragRefOffsetx += this.FIX_VP_X
|
|
|
+ this.itemMove(index, index + 1)
|
|
|
+ }
|
|
|
+
|
|
|
+ //向右下滑
|
|
|
+ private lowerRight(index: number): void {
|
|
|
+ if (!this.isDraggable(index + this.columnsCount + 1)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.offsetX -= this.FIX_VP_X
|
|
|
+ this.dragRefOffsetx += this.FIX_VP_X
|
|
|
+ this.offsetY -= this.FIX_VP_Y
|
|
|
+ this.dragRefOffsety += this.FIX_VP_Y
|
|
|
+ this.itemMove(index, index + this.columnsCount + 1)
|
|
|
+ }
|
|
|
+
|
|
|
+ //向右上滑
|
|
|
+ private upperRight(index: number): void {
|
|
|
+ if (!this.isDraggable(index - this.columnsCount + 1)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.offsetX -= this.FIX_VP_X
|
|
|
+ this.dragRefOffsetx += this.FIX_VP_X
|
|
|
+ this.offsetY += this.FIX_VP_Y
|
|
|
+ this.dragRefOffsety -= this.FIX_VP_Y
|
|
|
+ this.itemMove(index, index - this.columnsCount + 1)
|
|
|
+ }
|
|
|
+
|
|
|
+ //向左下滑
|
|
|
+ private lowerLeft(index: number): void {
|
|
|
+ const targetIndex = index + this.columnsCount - 1; // 正确目标索引是 2
|
|
|
+ if (!this.isDraggable(targetIndex)) { // 检查目标索引而非 index+2
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.offsetX += this.FIX_VP_X
|
|
|
+ this.dragRefOffsetx -= this.FIX_VP_X
|
|
|
+ this.offsetY -= this.FIX_VP_Y
|
|
|
+ this.dragRefOffsety += this.FIX_VP_Y
|
|
|
+ this.itemMove(index, targetIndex) // 使用计算后的目标索引
|
|
|
+ }
|
|
|
+
|
|
|
+ //向左上滑
|
|
|
+ private upperLeft(index: number): void {
|
|
|
+ if (!this.isDraggable(index - this.columnsCount - 1)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.offsetX += this.FIX_VP_X
|
|
|
+ this.dragRefOffsetx -= this.FIX_VP_X
|
|
|
+ this.offsetY += this.FIX_VP_Y
|
|
|
+ this.dragRefOffsety -= this.FIX_VP_Y
|
|
|
+ this.itemMove(index, index - this.columnsCount - 1)
|
|
|
+ }
|
|
|
+
|
|
|
+ private isDraggable(index: number): boolean {
|
|
|
+ return index < this.displayArr.length
|
|
|
+ }
|
|
|
+
|
|
|
+ // 新增辅助方法:检查当前项是否在最后一行
|
|
|
+ private isLastRow(index: number): boolean {
|
|
|
+ const rowIndex = Math.floor(index / this.columnsCount);
|
|
|
+ const totalRows = Math.ceil(this.displayArr.length / this.columnsCount);
|
|
|
+ return rowIndex === totalRows - 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否在第一行
|
|
|
+ private isFirstRow(index: number): boolean {
|
|
|
+ return Math.floor(index / this.columnsCount) === 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否在最后一列
|
|
|
+ private isLastColumn(index: number): boolean {
|
|
|
+ return index % this.columnsCount === this.columnsCount - 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否在第一列
|
|
|
+ private isFirstColumn(index: number): boolean {
|
|
|
+ return index % this.columnsCount === 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新sortOrder字段
|
|
|
+ private updateSortOrder(): void {
|
|
|
+ for (let i = 0; i < this.displayArr.length; i++) {
|
|
|
+ this.displayArr[i][this.sortOrder] = (i + 1).toString();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|