HarmonyOS 鸿蒙Next中实现拖拽容器大小变化

HarmonyOS 鸿蒙Next中实现拖拽容器大小变化 以下功能应该怎么实现呀,没有思路。

3 回复

【背景知识】

Grid组件是网格容器,其布局由行和列组成,可以通过设置layoutOptions参数,指定单元格做出不同的布局。组件可以通过设定手势处理,识别长按手势处理,识别长按、拖拽等手势并自定义响应动作。

PanGesture手势事件通过设置distance参数触发阈值,当滑动距离达到阈值时,通过事件回调参数GestureEvent可获取X/Y轴偏移量。onAreaChange是组件区域变化回调,当组件尺寸或位置发生改变时触发,可通过事件参数获取组件宽高、坐标等布局信息。

【解决方案】

  • 可以通过设定手势,实现item组件手动拖拽放大,并设定Grid组件layoutOptions参数的irregularIndexes,搭配onGetIrregularSizeByIndex,在item组件尺寸变化后重新布局。示例代码如下:
@Entry
@Component
struct GridItemChangeSizeByDrag {
  private gridData: number[] = [1, 2, 3, 4, 5, 6, 7, 8]
  @State itemWidths: number[] = []
  @State itemWidth: number = 0
  @State itemHeight: number = 0
  @State irregularIndexes: number[] = []
  @State firstLayout: boolean = true
  @State layoutOption: GridLayoutOptions = {
    regularSize: [1, 1]
  }

  aboutToAppear(): void {
    for (let i=0;i<this.gridData.length;i++) {
      this.itemWidths.push(0)
    }
  }

  build() {
    Grid(undefined, this.layoutOption) {
      ForEach(this.gridData, (num: number, index: number) => {
        GridItem() {
          Stack({ alignContent: Alignment.Start}) {
            Column()
              .width(this.itemWidths[index])
              .height(this.itemHeight)
              .backgroundColor(Color.Orange)
              .gesture(
                // 设置组合手势,当长按并拖拽时,改变item组件的宽度
                GestureGroup(GestureMode.Sequence,
                  LongPressGesture({ repeat: true, duration: 300 })
                    .onAction(() => {}),
                  PanGesture()
                    .onActionUpdate((event) => {
                      let idx = this.irregularIndexes.indexOf(index)
                      if (idx === -1) {
                        this.itemWidths[index] = this.itemWidth + event.offsetX
                      } else {
                        this.itemWidths[index] = this.itemWidth * 2 + event.offsetX
                      }
                    })
                    .onActionEnd(() => {
                      // 判断拖拽偏移量,并根据偏移量,改变Grid布局选项的参数
                      if (this.itemWidths[index] < this.itemWidth * 1.5) {
                        this.itemWidths[index] = this.itemWidth
                        let idx = this.irregularIndexes.indexOf(index)
                        if (idx !== -1) {
                          this.irregularIndexes.splice(idx)
                        }
                        this.layoutOption = {
                          regularSize: [1, 1],
                          irregularIndexes: this.irregularIndexes,
                          onGetIrregularSizeByIndex: () => [1, 1]  // 组件占布局的1行、1列
                        }
                      } else {
                        this.itemWidths[index] = this.itemWidth * 2
                        this.irregularIndexes.push(index)
                        this.layoutOption = {
                          regularSize: [1, 1],
                          irregularIndexes: this.irregularIndexes,
                          onGetIrregularSizeByIndex: () => [1, 2]  // 组件占布局的1行、2列
                        }
                      }
                    })
                )
              )

            Column() {
              Image($r('app.media.startIcon'))
                .height(24)
                .objectFit(ImageFit.Contain)
              Text(num.toString())
                .fontSize('16fp')
                .fontWeight(FontWeight.Bold)
            }
            .width(this.itemWidths[index] - 5) // 露出可拖拽组件的一边,用于拖拽放大
            .backgroundColor(Color.Gray)
            .justifyContent(FlexAlign.Center)
            .alignItems(HorizontalAlign.Center)
          }
        }
        .alignSelf(ItemAlign.Start)
        .onAreaChange((oldValue, newValue) => {
          if (this.firstLayout) {
            // 仅在第一个item变化时收集item的宽、高
            this.itemHeight = newValue.height as number
            this.itemWidth = newValue.width as number
            this.firstLayout = false
          }
          this.itemWidths[index] = newValue.width as number
        })
      })
    }
    .height(500)
    .width('100%')
    .padding(10)
    .backgroundColor(Color.Pink)
    .columnsTemplate('1fr 1fr')
    .rowsGap(5)
    .columnsGap(5)
    .maxCount(2)
  }
}
  • 通过onAreaChange回调获取容器尺寸参数,结合被拖动组件的尺寸限制计算位移边界阈值,在PanGesture的onActionUpdate回调中实时计算并约束组件位置偏移量。示例代码如下:
@Entry
@Component
struct DraggableDialog {
  @State offsetX: number = 0;
  @State offsetY: number = 0;
  @State startX: number = 0;
  @State startY: number = 0;
  @State outerContainerWidth: number = 0;
  @State outerContainerHeight: number = 0;
  private dialogWidth = 200;
  private dialogHeight = 150;
  private minX: number = 0;
  private maxX: number = 0;
  private minY: number = 0;
  private maxY: number = 0;

  build() {
    Stack() {
      // 弹框主体
      Column() {
        Text('可拖拽弹窗')
          .fontSize(20)
          .fontColor(Color.White)
          .margin({ bottom: 10 })

        Text('拖拽我移动位置')
          .fontSize(16)
          .fontColor(Color.White)
      }
      .width(this.dialogWidth)
      .height(this.dialogHeight)
      .backgroundColor(Color.Black)
      .borderRadius(10)
      .shadow({
        radius: 8,
        color: Color.Gray,
        offsetX: 5,
        offsetY: 5
      })
      .padding(20)
      .position({ x: this.offsetX, y: this.offsetY })
      .gesture(
        PanGesture()
          .onActionStart((event: GestureEvent) => {
            this.startX = this.offsetX;
            this.startY = this.offsetY;
          })
          .onActionUpdate((event: GestureEvent) => {
            this.offsetX = this.startX + event.offsetX;
            this.offsetY = this.startY + event.offsetY;
            // 判断坐标是否超出区域限定,超出后重新赋值
            if (this.offsetX <= this.minX) {
              this.offsetX = this.minX;
            }
            if (this.offsetX >= this.maxX) {
              this.offsetX = this.maxX;
            }
            if (this.offsetY <= this.minY) {
              this.offsetY = this.minY;
            }
            if (this.offsetY >= this.maxY) {
              this.offsetY = this.maxY;
            }
          })
      )

      // 页面背景文字
      Text(`页面背景\nX:${this.offsetX}\nY:${this.offsetY}`)
        .fontSize(30)
        .margin({ top: 300 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Orange)
    .onAreaChange((oldValue: Area, newValue: Area) => {
      // 限定区域宽高
      this.outerContainerWidth = Number(newValue.width);
      this.outerContainerHeight = Number(newValue.height);
      // 最大横纵坐标
      this.maxX = this.outerContainerWidth - this.dialogWidth;
      this.maxY = this.outerContainerHeight - this.dialogHeight;
    })
  }
}

【总结】

划分区域为上下左右四格,通过onAreaChange获取下组件变化后的xy的坐标,超过一定界限就通过设定手势处理的方法改变容器大小。

更多关于HarmonyOS 鸿蒙Next中实现拖拽容器大小变化的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中实现拖拽调整容器大小,可使用<PanGesture><Column>/<Row>组件组合。通过监听PanGestureonActionUpdate事件获取拖拽位移,动态修改容器的widthheight属性。示例代码框架:

@Entry
@Component
struct ResizableContainer {
  @State containerWidth: number = 200

  build() {
    Column() {
      Row()
        .width(this.containerWidth)
        .height(100)
        .backgroundColor(Color.Blue)

      PanGesture({ distance: 5 })
        .onActionUpdate((event: GestureEvent) => {
          this.containerWidth += event.offsetX
        })
        .height(30)
        .backgroundColor(Color.Gray)
    }
  }
}

核心是通过手势事件实时更新容器尺寸状态变量。

在HarmonyOS Next中实现拖拽容器大小变化,可以通过以下步骤实现:

  1. 使用PanGesture手势识别器监听拖拽操作
  2. 结合Column/Row和百分比布局实现动态尺寸调整
  3. 核心代码示例:
@Entry
@Component
struct ResizableContainer {
  @State dividerPosition: number = 0.5 // 初始分割比例

  build() {
    Column() {
      // 上部容器
      Column()
        .height(`${this.dividerPosition * 100}%`)
        .backgroundColor(Color.Red)
      
      // 可拖拽分割线
      Row()
        .height(20)
        .width('100%')
        .backgroundColor(Color.Gray)
        .gesture(
          PanGesture({ fingers: 1 })
            .onActionUpdate((event: GestureEvent) => {
              // 计算新位置并限制在0.1-0.9范围内
              this.dividerPosition = Math.max(0.1, 
                Math.min(0.9, 
                  this.dividerPosition + event.offsetY / 1000))
            })
        )
      
      // 下部容器
      Column()
        .height(`${(1 - this.dividerPosition) * 100}%`)
        .backgroundColor(Color.Blue)
    }
    .height('100%')
  }
}

关键点说明:

  1. 使用PanGesture捕获拖拽手势
  2. 通过offsetY计算位置变化量
  3. 使用State变量动态更新容器高度
  4. 添加比例限制防止容器过小

可以根据实际需求调整布局方向(水平/垂直)和样式。

回到顶部