HarmonyOS鸿蒙Next中Grid布局如何实现不规则item的拖拽交换功能?

HarmonyOS鸿蒙Next中Grid布局如何实现不规则item的拖拽交换功能?

  1. 可实现不规则图形的拖拽,1x,2x,4x等

  2. 拖动到顶部向上滚动,拖动到底部向下滚动

  3. 拖拽时的元素需要在手势放下时才回到交换位置

cke_1928.jpeg


更多关于HarmonyOS鸿蒙Next中Grid布局如何实现不规则item的拖拽交换功能?的实战教程也可以访问 https://www.itying.com/category-93-b0.html

8 回复

尊敬的开发者,您好!您的问题已受理,请您耐心等待,感谢您的理解与支持!

更多关于HarmonyOS鸿蒙Next中Grid布局如何实现不规则item的拖拽交换功能?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


开发步骤

  1. Grid布局及不同大小的GridItem界面开发。
Grid() {
  ForEach(this.numbers, (item: number) => {
    GridItem() {
      Stack({ alignContent: Alignment.TopEnd }) {
        Image(this.changeImage(item))
          .width('100%')
          .borderRadius(16)
          .objectFit(this.curBp === 'md' ? ImageFit.Fill : ImageFit.Cover)
          .draggable(false)
          .animation({ curve: Curve.Sharp, duration: 300 })
      }
    }
    .rowStart(0)
    .rowEnd(this.getRowEnd(item))
    .scale({ x: this.scaleItem === item ? 1.02 : 1, y: this.scaleItem === item ? 1.02 : 1 })
    .zIndex(this.dragItem === item ? 1 : 0)
    .translate(this.dragItem === item ? { x: this.offsetX, y: this.offsetY } : { x: 0, y: 0 })
    .hitTestBehavior(this.isDraggable(this.numbers.indexOf(item)) ? HitTestMode.Default : HitTestMode.None)
    // ...
  }, (item: number) => item.toString())
}
.width('100%')
.height('100%')
.editMode(true)
.scrollBar(BarState.Off)
.columnsTemplate('1fr 1fr')
.supportAnimation(true)
.columnsGap(12)
.rowsGap(12)
.enableScrollInteraction(true)
  1. 定义网格元素移动过程中的相关计算函数,其中itemMove()方法是实现元素交换重新排序的方法。
itemMove(index: number, newIndex: number): void {
  if (!this.isDraggable(newIndex)) {
    return;
  }
  let tmp = this.numbers.splice(index, 1);
  this.numbers.splice(newIndex, 0, tmp[0]);
  this.bigItemIndex = this.numbers.findIndex((item) => item === 0);
}
isInLeft(index: number) {
  if (index === this.bigItemIndex) {
    return index % 2 == 0;
  }
  if (this.bigItemIndex % 2 === 0) {
    if (index - this.bigItemIndex === 2 || index - this.bigItemIndex === 1) {
      return false;
    }
  } else {
    if (index - this.bigItemIndex === 1) {
      return false;
    }
  }
  if (index > this.bigItemIndex) {
    return index % 2 == 1;
  } else {
    return index % 2 == 0;
  }
}
down(index: number): void {
  if ([this.numbers.length - 1, this.numbers.length - 2].includes(index)) {
    return;
  }
  if (this.bigItemIndex - index === 1) {
    return;
  }
  if ([14, 15].includes(this.bigItemIndex) && this.bigItemIndex === index) {
    return;
  }
  this.offsetY -= this.FIX_VP_Y;
  this.dragRefOffSetY += this.FIX_VP_Y;
  if (index - 1 === this.bigItemIndex) {
    this.itemMove(index, index + 1);
  } else {
    this.itemMove(index, index + 2);
  }
}
up(index: number): void {
  if (!this.isDraggable(index - 2)) {
    return;
  }
  if (index - this.bigItemIndex === 3) {
    return;
  }
  this.offsetY += this.FIX_VP_Y;
  this.dragRefOffSetY -= this.FIX_VP_Y;
  if (this.bigItemIndex === index) {
    this.itemMove(index, index - 2);
  } else {
    if (index - 2 === this.bigItemIndex) {
      this.itemMove(index, index - 1);
    } else {
      this.itemMove(index, index - 2);
    }
  }
}
left(index: number): void {
  if (this.bigItemIndex % 2 === 0) {
    if (index - this.bigItemIndex === 2) {
      return;
    }
  }
  if (this.isInLeft(index)) {
    return;
  }
  if (!this.isDraggable(index - 1)) {
    return;
  }
  this.offsetX += this.FIX_VP_X;
  this.dragRefOffSetX -= this.FIX_VP_X;
  this.itemMove(index, index - 1)
}
right(index: number): void {
  if (this.bigItemIndex % 2 === 1) {
    if (index - this.bigItemIndex === 1) {
      return;
    }
  }
  if (!this.isInLeft(index)) {
    return;
  }
  if (!this.isDraggable(index + 1)) {
    return;
  }
  this.offsetX -= this.FIX_VP_X;
  this.dragRefOffSetX += this.FIX_VP_X;
  this.itemMove(index, index + 1)
}
isDraggable(index: number): boolean {
  return index >= 0;
}
  1. GridItem绑定组合手势:长按,拖拽。并在手势的回调函数中设置显式动画。
.gesture(
  GestureGroup(GestureMode.Sequence,
    LongPressGesture({ repeat: true })
      .onAction(() => {
        this.getUIContext().animateTo({ curve: Curve.Friction, duration: 300 }, () => {
          this.scaleItem = item;
        })
      })
      .onActionEnd(() => {
        this.getUIContext().animateTo({ curve: Curve.Friction, duration: 300 }, () => {
          this.scaleItem = -1;
        })
      }),
    PanGesture({ fingers: 1, direction: null, distance: 0 })
      .onActionStart(() => {
        this.dragItem = item;
        this.dragRefOffSetX = 0;
        this.dragRefOffSetY = 0;
      })
      .onActionUpdate((event: GestureEvent) => {
        this.offsetX = event.offsetX - this.dragRefOffSetX;
        this.offsetY = event.offsetY - this.dragRefOffSetY;
        this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
          let index = this.numbers.indexOf(this.dragItem);
          if (this.offsetY >= this.FIX_VP_Y / 2 && (this.offsetX <= 44 && this.offsetX >= -44)) {
            this.down(index);
          } else if (this.offsetY <= -this.FIX_VP_Y / 2 && (this.offsetX <= 44 && this.offsetX >= -44)) {
            this.up(index);
          } else if (this.offsetX >= this.FIX_VP_X / 2 && (this.offsetY <= 50 && this.offsetY >= -50)) {
            this.right(index);
          } else if (this.offsetX <= -this.FIX_VP_Y / 2 && (this.offsetY <= 50 && this.offsetY >= -50)) {
            this.left(index);
          }
        })
      })
      .onActionEnd(() => {
        this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
          this.dragItem = -1;
        })
        this.getUIContext().animateTo({ curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150 }, () => {
          this.scaleItem = -1;
        })
      })
  )
    .onCancel(() => {
      this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
        this.dragItem = -1;
      })
      this.getUIContext().animateTo({ curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150 }, () => {
        this.scaleItem = -1;
      })
    })
)

最后效果:

效果图

这就是最佳实践Grid拖拽交换那个例子吗?已经看过有很多问题,后面不是有备注:“当前方案仅适用于页面包含一个较大网格元素的布局。”,也不支持滚动拖动,实际场景有三种类型大小的网格,

效果看着可以,但是列表不是grid组件类型,如果数据量太大不好做组件复用吧,

在HarmonyOS Next中实现Grid不规则item拖拽交换,需使用GridContainer组件配合拖拽事件。通过onDragStart设置拖拽数据,在onDrop事件中获取目标位置信息。利用GridContainer的swapItems方法交换两个item位置,swapItems接受源索引和目标索引参数。拖拽过程中需动态计算不规则item的占位规则,可通过设置不同row/column span实现。使用GridContainerLayoutConfig配置item布局参数,结合DragEvent处理拖拽交互逻辑。

在HarmonyOS Next中实现不规则Grid拖拽交换功能,可通过以下方案:

  1. 自定义Grid布局
    使用GridContainer配合GridRow/GridCol构建基础网格,通过columnStart/columnEndrowStart/rowEnd定义不规则item的占位(如1x1、2x2、4x4)。需重写布局算法,动态计算每个item的实际位置和尺寸。

  2. 拖拽事件处理

  • 使用PanGesture监听拖拽手势
  • 通过onActionStart创建拖拽镜像视图
  • onActionUpdate中实时更新镜像位置,并触发滚动检测
  • onActionEnd执行最终位置交换
  1. 滚动边界检测
    在拖拽过程中:
  • 当拖拽位置到达顶部/底部阈值时,调用Scroll组件的scrollBy方法触发滚动
  • 建议设置滚动加速度,距离边界越近滚动速度越快
  1. 位置交换逻辑
  • 使用矩阵映射记录每个网格单元的状态
  • 拖拽结束时计算目标网格位置
  • 执行动画交换:先缩小原item,再平移新位置,最后恢复尺寸
  • 更新Grid数据源并刷新布局

关键代码片段:

// 手势处理
PanGesture(this.panOption)
  .onActionStart((event: GestureEvent) => {
    this.showDragView(event.offsetX, event.offsetY);
  })
  .onActionUpdate((event: GestureEvent) => {
    this.updateDragPosition(event.offsetX, event.offsetY);
    this.checkScrollBoundary(event.offsetY);
  })
  .onActionEnd(() => {
    this.executeSwapAnimation();
  })

注意事项:

  • 需要维护网格状态矩阵来跟踪每个单元格的占用情况
  • 拖拽镜像建议使用@Reusable优化性能
  • 交换动画需考虑不规则item的尺寸变化插值

这种方案能完整实现不规则网格拖拽交换,同时支持边界滚动和自然的手势交互。

回到顶部