HarmonyOS鸿蒙Next中list嵌套grid,子item手势拖拽,如何设置可以拖拽超出grid的大小

HarmonyOS鸿蒙Next中list嵌套grid,子item手势拖拽,如何设置可以拖拽超出grid的大小

由于复杂业务逻辑,拖拽只能用通用手势PanGesture来做,然后给gridItem拖拽时候,拖动到grid外就看不到了,如何可以让item可以自由拖动到屏幕任意位置

9 回复

Grid组件默认clip属性为true,不支持将组件拖拽至Grid外部,在你的代码中,为Grid设置属性.clip(false)就可以将GridItem拖拽出去了。

更多关于HarmonyOS鸿蒙Next中list嵌套grid,子item手势拖拽,如何设置可以拖拽超出grid的大小的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


管用,果然是高手,解决了我困扰很久的大问题,

【背景知识】 Grid组件通用属性clip的默认值为true,不允许GridItem子组件超Grid范围。

【解决方案】 Grid组件通用属性clip的默认值为true,为Grid组件设置clip属性值为false值即可。

示例代码:

import { curves } from '@kit.ArkUI'

@ComponentV2
struct ListViewPage {
  @Local listData: string[] = []

  aboutToAppear(): void {
    for (let index = 0; index < 9; index++) {
      this.listData.push('itemIndex=' + index)
    }
  }

  build() {
    List() {
      Repeat(this.listData)
        .each((obj: RepeatItem<string>) => {
          ListItem() {
            Column() {
              Text(obj.item).padding(10)
              GridPage()
            }
          }
        })
        .virtualScroll()
        .key((item: string) => item)
    }
    .height('100%')
    .width('100%')
  }
}

@Component
export struct GridPage {
  //元素数组
  @State numbers: number[] = [];
  @State row: number = 4;
  //元素数组中最后一个元素的索引
  @State lastIndex: number = 0;
  @State dragItem: number = -1;
  @State scaleItem: number = -1;
  @State item: number = -1;
  @State offsetX: number = 0;
  @State offsetY: number = 0;
  // row 设置网格列数
  private str: string = '';
  private dragRefOffsetx: number = 0;
  private dragRefOffsety: number = 0;
  private FIX_VP_X: number = 108;
  private FIX_VP_Y: number = 120;

  aboutToAppear() {
    for (let i = 1; i <= 8; i++) {
      this.numbers.push(i);
    }
    this.lastIndex = this.numbers.length - 1;

    // 多列
    for (let i = 0; i < this.row; i++) {
      this.str = this.str + '1fr ';
    }
  }

  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]);
  }

  //向下滑
  down(index: number): void {
    // 指定固定GridItem不响应事件
    if (!this.isDraggable(index + this.row)) {
      return;
    }
    this.offsetY -= this.FIX_VP_Y;
    this.dragRefOffsety += this.FIX_VP_Y;
    // 多列
    this.itemMove(index, index + this.row);
  }

  //向下滑(右下角为空)
  down2(index: number): void {
    if (!this.isDraggable(index + 3)) {
      return;
    }
    this.offsetY -= this.FIX_VP_Y;
    this.dragRefOffsety += this.FIX_VP_Y;
    this.itemMove(index, index + 3);
  }

  //向上滑
  up(index: number): void {
    if (!this.isDraggable(index - this.row)) {
      return;
    }
    this.offsetY += this.FIX_VP_Y;
    this.dragRefOffsety -= this.FIX_VP_Y;
    this.itemMove(index, index - this.row);
  }

  //向左滑
  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);
  }

  //向右滑
  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);
  }

  //向右下滑
  lowerRight(index: number): void {
    if (!this.isDraggable(index + this.row + 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.row + 1);
  }

  //向右上滑
  upperRight(index: number): void {
    if (!this.isDraggable(index - (this.row - 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.row - 1));
  }

  //向左下滑
  lowerLeft(index: number): void {
    if (!this.isDraggable(index + (this.row - 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.row - 1));
  }

  //向左上滑
  upperLeft(index: number): void {
    if (!this.isDraggable(index - (this.row + 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.row + 1));
  }

  //通过元素的索引,控制对应元素是否能移动排序
  isDraggable(index: number): boolean {
    return index > -1; //恒成立,所有元素均可移动排序
  }

  build() {
    Grid() {
      ForEach(this.numbers, (item: number) => {
        GridItem() {
          Text(item + '').fontSize(16).width('100%').textAlign(TextAlign.Center).height(100).borderRadius(10).backgroundColor(0xFFFFFF).shadow(
            this.scaleItem == item ? {
              radius: 70,
              color: '#15000000',
              offsetX: 0,
              offsetY: 0
            } :
              {
                radius: 0,
                color: '#15000000',
                offsetX: 0,
                offsetY: 0
              }
          ).animation({
            curve: Curve.Sharp,
            duration: 300
          })
        }
        .onAreaChange((_oldVal, newVal) => {
          // 多列
          this.FIX_VP_X = Math.round(newVal.width as number);
          this.FIX_VP_Y = Math.round(newVal.height as number);
        })
        // 指定固定GridItem不响应事件
        .hitTestBehavior(this.isDraggable(this.numbers.indexOf(item)) ? HitTestMode.Default : HitTestMode.None)
        .scale({ x: this.scaleItem === item ? 1.05 : 1, y: this.scaleItem === item ? 1.05 : 1 })
        .zIndex(this.dragItem === item ? 1 : 0)
        .translate(this.dragItem === item ? { x: this.offsetX, y: this.offsetY } : { x: 0, y: 0 })
        .padding(10)
        .gesture(
          //以下组合手势为顺序识别,当长按手势事件未正常触发时则不会触发拖动手势事件
          GestureGroup(GestureMode.Sequence,
            LongPressGesture({
              repeat: true,
              duration: 50
            })//控制触发拖动的长按事件的时间,默认500毫秒,设置小于0为默认值,这里设置为50毫秒
              .onAction(() => {
                animateTo({
                  curve: Curve.Friction,
                  duration: 300
                }, () => {
                  this.scaleItem = item;
                });
              })
              .onActionEnd(() => {
                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.offsetY = event.offsetY - this.dragRefOffsety;
                this.offsetX = event.offsetX - this.dragRefOffsetx;

                animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
                  let index = this.numbers.indexOf(this.dragItem);
                  //  44  宽度一半  减间距
                  if (this.offsetY >= this.FIX_VP_Y / 2 &&
                    (this.offsetX <= this.FIX_VP_X / 2 && this.offsetX >= -this.FIX_VP_X / 2)
                    && (index + this.row <= this.lastIndex)) {
                    //向下滑
                    this.down(index);
                  } else if (this.offsetY <= -this.FIX_VP_Y / 2 &&
                    (this.offsetX <= this.FIX_VP_X / 2 && this.offsetX >= -this.FIX_VP_X / 2)
                    && index - this.row >= 0) {
                    //向上滑
                    this.up(index);
                  } else if (this.offsetX >= this.FIX_VP_X / 2 &&
                    (this.offsetY <= this.FIX_VP_Y / 2 && this.offsetY >= -this.FIX_VP_Y / 2)
                    && !(((index - (this.row - 1)) % this.row === 0) || index === this.lastIndex)) {
                    // ) {
                    //向右滑
                    this.right(index);
                  } else if (this.offsetX <= -this.FIX_VP_X / 2 &&
                    (this.offsetY <= this.FIX_VP_Y / 2 && this.offsetY >= -this.FIX_VP_Y / 2)
                    && !(index % this.row === 0)) {
                    //向左滑
                    this.left(index);
                  } else if (this.offsetX >= this.FIX_VP_X / 2 && this.offsetY >= this.FIX_VP_Y / 2
                    && ((index + this.row + 1 <= this.lastIndex && !((index - (this.row - 1)) % this.row === 0)) ||
                      !((index - (this.row - 1)) % this.row === 0))) {
                    //向右下滑
                    this.lowerRight(index);
                  } else if (this.offsetX >= this.FIX_VP_X / 2 && this.offsetY <= -this.FIX_VP_Y / 2
                    && !((index - this.row < 0) || ((index - (this.row - 1)) % this.row === 0))) {
                    //向右上滑
                    this.upperRight(index);
                  } else if (this.offsetX <= -this.FIX_VP_X / 2 && this.offsetY >= this.FIX_VP_Y / 2
                    && (!(index % this.row === 0) && (index + (this.row - 1) <= this.lastIndex))) {
                    //向左下滑
                    this.lowerLeft(index);
                  } else if (this.offsetX <= -this.FIX_VP_X / 2 && this.offsetY <= -this.FIX_VP_Y / 2
                    && !((index <= this.row - 1) || (index % this.row === 0))) {
                    //向左上滑
                    this.upperLeft(index);
                  } else if (this.offsetX >= this.FIX_VP_X / 2 && this.offsetY >= this.FIX_VP_Y / 2
                    && (index === this.lastIndex)) {
                    //向右下滑(右下角为空)
                    this.down2(index);
                  }
                });
              })
              .onActionEnd(() => {
                animateTo({
                  curve: curves.interpolatingSpring(0, 1, 400, 38)
                }, () => {
                  this.dragItem = -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;
              });
              animateTo({
                curve: curves.interpolatingSpring(14, 1, 170, 17)
              }, () => {
                this.scaleItem = -1;
              });
            })
        )
      }, (item: number) => item.toString())
    }
    .editMode(true)
    .scrollBar(BarState.Off)
    // 多列
    .columnsTemplate(this.str)
    .width('100%')
    // 设置clip属性,允许子组件超出范围
    .clip(false)
    .backgroundColor('#0D182431')
    .padding({ top: 5 })
  }
}

拖拽是要改变item大小,还是拖拽移动位置?有demo代码吗?不然不太好帮你处理

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:BV1S4411E7LY/?p=17

我重新发了一个帖子,是拖拽grid中的griditem,

您好,为了更快速解决您的问题,并且吸引更多用户一同参与您问题的解答与讨论,建议您补全如下信息:

  • 补全复现代码(如最小复现demo),让参与用户更快速复现您的问题;
  • 更多提问技巧,请参考:【Tips】如何提个好问题

在HarmonyOS Next中实现Grid子项拖拽超出容器范围,需使用GridonDragStartonDragMove回调配合绝对定位。设置Gridclip属性为false允许内容溢出,通过position: {x,y}动态更新拖拽项位置。示例代码片段:

@Entry
@Component
struct DragGrid {
  @State items: number[] = [1, 2, 3, 4]
  @State dragX: number = 0
  @State dragY: number = 0
  @State isDragging: boolean = false

  build() {
    Grid() {
      ForEach(this.items, item => {
        GridItem() {
          Text(`${item}`)
            .onTouch((event: TouchEvent) => {
              if (event.type === TouchType.Move) {
                this.dragX = event.touches[0].windowX
                this.dragY = event.touches[0].windowY
                this.isDragging = true
              }
            })
        }
        .position({ x: this.isDragging ? this.dragX : 0, y: this.isDragging ? this.dragY : 0 })
      })
    }
    .clip(false)
  }
}

在HarmonyOS Next中实现Grid子项自由拖拽到屏幕任意位置,可以通过以下方案解决:

  1. 使用绝对布局+转换矩阵:
  • 将GridItem的布局方式改为绝对定位
  • 通过PanGesture的onActionUpdate回调获取位移值
  • 使用matrix.translate()方法动态更新Item位置

关键代码示例:

@State itemPosition: Position = { x: 0, y: 0 }

PanGesture({ minimumDistance: 5 })
  .onActionUpdate((event: GestureEvent) => {
    this.itemPosition = {
      x: this.itemPosition.x + event.offsetX,
      y: this.itemPosition.y + event.offsetY
    }
  })

GridItem() {
  // 内容
}
.position({ x: px2vp(this.itemPosition.x), y: px2vp(this.itemPosition.y) })
  1. 层级提升方案:
  • 将拖拽Item提升到Page级别而非Grid内部
  • 通过zIndex控制显示层级
  • 使用Stack布局覆盖在Grid上方
  1. 注意事项:
  • 需要处理与其他组件的遮挡关系
  • 拖拽结束后可能需要复位或持久化位置
  • 考虑添加动画过渡效果提升体验

建议配合使用HitTestBehavior控制触摸穿透,确保底层Grid的交互不受影响。

回到顶部