HarmonyOS鸿蒙Next中如何实现长按后拖动组件

HarmonyOS鸿蒙Next中如何实现长按后拖动组件 cke_141.png

RelativeContainer里放了地图, 这个绿圈,还有拖拽那个图标,还有下面那个显示直径的。

拖拽那个图标和显示直径都是相对于,绿圈来定位的。如何实现在长按拖动这个拖拽图标,来改变绿圈的宽高。

代码大致如下

build() {
  RelativeContainer() {
    // 纯地图组件高德的
    CommonMapView()
      .width('100%')
      .height('100%')
      .onAreaChange((oldValue: Area, newValue: Area) => {
        this.area = newValue
      })
      .id('mapView')

    // 一个系统的圈或者系统的矩形
    if (this.geoFenceInfo?.shape === GeoFenceShape.CIRCLE) {
      Circle()
        .width(this.circleWH)
        .height(this.circleWH)
        .stroke(this.getStrokeColor())
        .strokeWidth(px2vp(8))
        .fill(this.getFillColor())
        .id('showShapeCircle')
        .alignRules({
          center: { anchor: "mapView", align: VerticalAlign.Center },
          middle: { anchor: "mapView", align: HorizontalAlign.Center },
        })
        .onAreaChange((oldValue, newValue) => {
          this.screenCircleArea = newValue
        })
    } else {

      Rect()
        .width(this.rectangleW)
        .height(this.rectangleH)
        .stroke(this.getStrokeColor())
        .strokeWidth(px2vp(8))
        .fill(this.getFillColor())
        .id('showShapeRectangle')
        .alignRules({
          center: { anchor: "mapView", align: VerticalAlign.Center },
          middle: { anchor: "mapView", align: HorizontalAlign.Center },
        })
    }

    // 中心Marker
    Image($r('app.media.ic_vc_geo_fence_center'))
      .objectFit(ImageFit.None)
      .width(32)
      .height(36)
      .id('showShapeCenterMarker')
      .alignRules({
        center: {
          anchor: this.geoFenceInfo?.shape === GeoFenceShape.CIRCLE ? 'showShapeCircle' : 'showShapeRectangle',
          align: VerticalAlign.Center
        },
        middle: {
          anchor: this.geoFenceInfo?.shape === GeoFenceShape.CIRCLE ? 'showShapeCircle' : 'showShapeRectangle',
          align: HorizontalAlign.Center
        },
      })

    // 右上角的拖动缩放
    Image($r('app.media.ic_vc_geo_size_edit'))
      .objectFit(ImageFit.None)
      .width(this.sizeEditIconWH)
      .height(this.sizeEditIconWH)
      .id('showShapeDrag')
      .alignRules({
        top: {
          anchor: this.geoFenceInfo?.shape === GeoFenceShape.CIRCLE ? 'showShapeCircle' : 'showShapeRectangle',
          align: VerticalAlign.Top
        },
        right: {
          anchor: this.geoFenceInfo?.shape === GeoFenceShape.CIRCLE ? 'showShapeCircle' : 'showShapeRectangle',
          align: HorizontalAlign.End
        },
      })
      .offset({
        x: this.geoFenceInfo?.shape === GeoFenceShape.CIRCLE ?
          -(Number(this.screenCircleArea?.width ?? 0) * 0.425 / 4) + this.sizeEditIconWH * 0.5 :
          this.sizeEditIconWH * 0.5,
        y: this.geoFenceInfo?.shape === GeoFenceShape.CIRCLE ?
          Number(this.screenCircleArea?.width ?? 0) * 0.425 / 4 - this.sizeEditIconWH * 0.5 :
          -this.sizeEditIconWH * 0.5,
      })
      .gesture(
            //这里的代码好像有问题,应该用手指组,但是加了长按手势后一直走cancel
          PanGesture({ fingers: 1 })
            .onActionStart((_event) => {

            })
            .onActionUpdate(event => {
              animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
                console.log(`offsetX: ${event.offsetX}, offsetY: ${event.offsetY}`)
                if (this.geoFenceInfo?.shape === GeoFenceShape.CIRCLE) {
                  let offsetDistance = Math.min(event.offsetX, event.offsetY)
                  this.circleWH = Math.max(this.circleWH + offsetDistance, 0);
                } else {
                  this.rectangleW = Math.max(this.rectangleW + event.offsetX, 0);
                  this.rectangleH = Math.max(this.rectangleH + event.offsetY, 0);

                }
                this.handleChange()
              })
            })
            .onActionEnd(() => {
               

              this.reset()
            })

      )


    // 中心下文的直径或者宽高显示
    Text(this.getSizeTagText())
      .fontSize(14)
      .fontColor($r('app.color.color_000000'))
      .borderWidth(1)
      .borderColor($r('app.color.color_CCCCCC'))
      .backgroundColor($r('app.color.color_FFFFFF'))
      .padding({
        left: 12,
        top: 4,
        right: 12,
        bottom: 4
      })
      .onAreaChange((oldValue, newValue) => {
        this.sizeTagArea = newValue
      })
      .alignRules({
        bottom: {
          anchor: this.geoFenceInfo?.shape === GeoFenceShape.CIRCLE ? 'showShapeCircle' : 'showShapeRectangle',
          align: VerticalAlign.Bottom
        },
        middle: {
          anchor: this.geoFenceInfo?.shape === GeoFenceShape.CIRCLE ? 'showShapeCircle' : 'showShapeRectangle',
          align: HorizontalAlign.Center
        },
      })
      .offset({ x: 0, y: (Number(this.sizeTagArea?.height ?? 0) * 0.5) })

  }
}

更多关于HarmonyOS鸿蒙Next中如何实现长按后拖动组件的实战教程也可以访问 https://www.itying.com/category-93-b0.html

5 回复

【背景知识】

组合手势:手势识别组合,即两种及以上手势组合为复合手势,支持顺序识别、并发识别和互斥识别。

【参考方案】

可参考组件拖拽移动及放大缩小示例,通过组合手势实现组件跟手拖拽移动效果,通过修改Grid的属性实现组件的放大缩小效果。

  1. 通过组合手势实现组件跟手拖拽移动效果,当组件移动到对应位置时刷新组件排列。

    // 手势传出组件移动信息
    .gesture(
      GestureGroup(GestureMode.Sequence,
        LongPressGesture({ repeat: true })
          .onAction((event?: GestureEvent) => {
            if (this.selectedItem === undefined) {
              this.selectedItem = item
              // 记录起始位置
              this.dragStartX = Number(event?.target.area.globalPosition.x) - item.width / 2 
              this.dragStartY = Number(event?.target.area.globalPosition.y) - item.height / 2
            }
          })
          ...
        PanGesture({ fingers: 1 })
          ...
          .onActionUpdate(event => {
            // 记录偏移量
            this.offsetY = event.offsetY 
            this.offsetX = event.offsetX
            this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
              this.handleDrag() // 处理拖拽函数
            })
          })
          ...
      })
    )
    ...
    // 将组件移动到首位,刷新组件排列
    let tmp = this.gridItems.splice(index, 1)
    this.gridItems.splice(0, 0, tmp[0])
    this.positionMode = PositionMode.COLUMNS
    this.setRowsColumns()
    
  2. 通过修改组件的宽高,同步修改GridrowsTemplate以及columnsTemplate参数,实现组件的放大缩小效果。

    // 修改第一、二行的高度
    this.columnsFirstHeightTmp = this.columnsFirstHeight + this.offsetY
    this.columnsSecondHeightTmp = this.columnsSecondHeight - this.offsetY
    ...
    // 同步修改grid的rowsTemplate以及columnsTemplate参数
    this.rowsT = this.columnsFirstHeightTmp + 'fr ' + this.columnsSecondHeightTmp + 'fr ' +
      (this.fullHeightTmp - this.columnsFirstHeightTmp - this.columnsSecondHeightTmp) + 'fr'
    this.columnsT = '1fr'
    

更多关于HarmonyOS鸿蒙Next中如何实现长按后拖动组件的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


实现长按拖动调整组件尺寸的功能,需要结合手势识别与动态布局更新。

优化步骤

1/使用组合手势替代单独PanGesture:

.gesture(
  GestureGroup(
    GesturePriority.Low,
    LongPressGesture({ repeat: true })
      .onAction(() => {
        console.log('LongPress triggered')
      }),
    PanGesture({ fingers: 1, distance: 5 })
      .onActionUpdate(event => {
        // 处理拖动逻辑
      })
  )
)

2/ 动态尺寸计算

//记录初始尺寸
@State startWidth: number = 0
@State startHeight: number = 0

// 在onActionStart中初始化
.onActionStart(() => {
  this.startWidth = this.circleWH
  this.startHeight = this.circleWH
})
......

//增量计算逻辑优化
.onActionUpdate(event => {
  const delta = Math.sqrt(event.offsetX ** 2 + event.offsetY ** 2)
  const newSize = this.startWidth + delta * 2
  this.circleWH = Math.max(newSize, MIN_CIRCLE_SIZE)
})

3/建立组件间联动关系:

Circle()
  .width(this.circleWH)
  .height(this.circleWH)
  .onAreaChange((_, newArea) => {
    // 更新关联组件位置
    this.sizeEditIconWH = newArea.width * 0.1
  })

根据鸿蒙文档,实现长按后拖动组件来改变绿圈宽高的功能,需要结合手势处理和相关事件回调。

实现方案

在RelativeContainer中,长按拖动图标来改变绿圈(Circle或Rect)的宽高,可以通过使用手势组(GestureGroup)组合长按手势(LongPressGesture)和拖动手势(PanGesture)来实现。文档中提到,拖拽事件通常涉及长按500毫秒后开始拖动,但如果您希望更直接地控制长按和拖动的顺序,使用手势组可以避免手势冲突。

关键步骤:

  1. 设置手势组:将长按手势和拖动手势组合成顺序手势(GestureMode.Sequence),确保长按后才触发拖动。
  2. 处理拖动更新:在PanGesture的onActionUpdate回调中,根据拖动偏移量(offsetX和offsetY)实时更新绿圈的宽高。
  3. 使用animateTo平滑动画:为了平滑的尺寸变化,使用animateTo并设置合适的曲线。

代码修改建议:

在您的代码中,拖拽图标(Image组件)的gesture修饰符应改为使用GestureGroup,如下所示:

.gesture(
  GestureGroup(
    GestureMode.Sequence, // 顺序手势:先长按后拖动
    LongPressGesture()
      .onAction(() => {
        // 长按触发时的操作(可选),例如显示提示或开始拖动准备
        console.log("LongPress triggered");
      }),
    PanGesture({ fingers: 1 })
      .onActionStart(() => {
        // 拖动开始时的操作(可选),例如记录初始状态
      })
      .onActionUpdate((event: GestureEvent) => {
        // 根据拖动偏移量更新绿圈尺寸
        animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
          if (this.geoFenceInfo?.shape === GeoFenceShape.CIRCLE) {
            // 对于圆形,取offsetX和offsetY的最小值作为偏移距离,以保持圆形比例
            let offsetDistance = Math.min(event.offsetX, event.offsetY);
            this.circleWH = Math.max(this.circleWH + offsetDistance, 0);
          } else {
            // 对于矩形,分别更新宽度和高度
            this.rectangleW = Math.max(this.rectangleW + event.offsetX, 0);
            this.rectangleH = Math.max(this.rectangleH + event.offsetY, 0);
          }
          this.handleChange(); // 您的自定义处理函数
        });
      })
      .onActionEnd(() => {
        // 拖动结束时的操作(可选),例如重置状态或提交更改
        this.reset();
      })
  )
)

注意事项:

  • 手势冲突解决:使用GestureMode.Sequence可以确保长按后才触发拖动,避免单独添加长按手势导致的cancel问题。
  • 文档支持:根据文档《arkts-gesture-events-gesture-judge.md》,手势判断可以通过onGestureJudgeBegin处理,但对于简单场景,手势组更直接。
  • 性能考虑:拖动过程中频繁更新尺寸可能影响性能,建议使用animateTo优化动画效果。
  • 边界处理:确保尺寸不会变为负数(使用Math.max(0, value))。

完整代码上下文:

将上述gesture代码替换到您的拖拽图标Image组件中,其他部分保持不变。例如:

Image($r('app.media.ic_vc_geo_size_edit'))
  // ...其他属性(width、height、alignRules等)
  .gesture(
    GestureGroup(
      GestureMode.Sequence,
      LongPressGesture().onAction(() => {}),
      PanGesture({ fingers: 1 })
        .onActionUpdate((event) => {
          // 更新尺寸逻辑
        })
    )
  )

为什么这样实现?

  • 文档中提到拖拽事件需要长按500毫秒,但使用手势组可以更灵活地控制长按时间和拖动触发。
  • PanGesture提供了直接的偏移量(offsetX和offsetY),便于计算尺寸变化。
  • 如果您需要更系统的拖拽事件(如onDragStart),可以参考文档《ts-universal-events-drag-drop.md》,但对于本地拖动和实时尺寸更新,手势组更简单高效。

在HarmonyOS Next中,实现长按后拖动组件主要使用PanGesture(拖动手势)和LongPressGesture(长按手势)进行组合。

核心步骤:

  1. 为组件同时绑定LongPressGesturePanGesture
  2. LongPressGestureonAction回调中,触发拖动的开始状态(例如,显示拖动效果或记录初始位置)。
  3. PanGestureonActionStartonActionUpdateonActionEnd回调中,通过event.offsetXevent.offsetY实时更新组件的位置(通常使用绝对定位position或偏移量translate)。
  4. 通过手势的responseRegion属性可以调整手势的响应区域。

示例代码片段:

// 使用@State变量记录组件偏移量
@State offsetX: number = 0;
@State offsetY: number = 0;

// 在组件上绑定组合手势
.gesture(
  GestureGroup(
    GestureMode.Exclusive,
    LongPressGesture({ repeat: false })
      .onAction((event: GestureEvent) => {
        // 长按触发,可在此处设置拖动开始状态
        console.info('LongPress onAction');
      }),
    PanGesture()
      .onActionStart((event: GestureEvent) => {
        // 拖动开始
      })
      .onActionUpdate((event: GestureEvent) => {
        // 拖动中,更新位置
        this.offsetX = event.offsetX;
        this.offsetY = event.offsetY;
      })
      .onActionEnd(() => {
        // 拖动结束
      })
  )
)
// 应用偏移到组件
.translate({ x: this.offsetX, y: this.offsetY })

通过组合长按和拖动手势,并实时更新组件位置,即可实现长按后拖动的交互效果。

在HarmonyOS Next中实现长按后拖动组件来改变绿圈大小,核心是正确使用手势组合。你的代码问题在于PanGesture没有与长按手势组合使用。

以下是修改方案:

  1. 使用手势组合:将长按手势和拖动手势组合起来
.gesture(
  GestureGroup(
    GestureMode.Sequence,
    LongPressGesture({ fingers: 1, repeat: false })
      .onAction(() => {
        // 长按开始,可以在这里显示拖动提示
      }),
    PanGesture({ fingers: 1 })
      .onActionStart(() => {
        // 拖动开始
      })
      .onActionUpdate((event) => {
        // 计算新的尺寸
        if (this.geoFenceInfo?.shape === GeoFenceShape.CIRCLE) {
          // 圆形:根据对角线移动计算直径变化
          let delta = Math.sqrt(event.offsetX ** 2 + event.offsetY ** 2);
          this.circleWH = Math.max(this.circleWH + delta, minSize);
        } else {
          // 矩形:分别处理宽高
          this.rectangleW = Math.max(this.rectangleW + event.offsetX, minWidth);
          this.rectangleH = Math.max(this.rectangleH + event.offsetY, minHeight);
        }
      })
      .onActionEnd(() => {
        // 拖动结束,保存状态
      })
  )
)
  1. 关键点说明
  • GestureMode.Sequence确保先长按后拖动
  • 圆形缩放应基于对角线距离计算,而不是单独取X或Y偏移
  • 需要设置最小尺寸限制防止过度缩小
  1. 性能优化
  • 移除animateTo动画,直接更新状态变量
  • 使用@State装饰器确保UI响应式更新
  • 避免在onActionUpdate中频繁创建对象
  1. 相对定位保持: 由于使用RelativeContainer,拖拽图标会自动跟随绿圈位置变化,只需更新绿圈的尺寸变量即可。

这种实现方式既满足了长按触发需求,又保证了拖动的流畅性。

回到顶部