HarmonyOS 鸿蒙Next中如何实现弹窗整体拖动?

HarmonyOS 鸿蒙Next中如何实现弹窗整体拖动? 目前主要使用的是CustomDialogController自定义弹窗,大概看了下API,没有发现能支持拖拽的。请问如何实现?非必要不会替换目前使用的CustomDialog。

6 回复

通过 offset 偏移量去控制对话框的位置,使用 PanGesture 让对话框支持拖拽,同时修改 offset 值。同时,确保对话框的四边都不超出窗口边界:

cke_4135.gif

@CustomDialog
struct DraggableDialog {
  controller: CustomDialogController
  @State offsetX: number = 0
  @State offsetY: number = 0
  private lastX: number = 0
  private lastY: number = 0
  @State dialogW: number = 280
  @State dialogH: number = 200
  windowWidth: number = 360
  windowHeight: number = 640

  private clamp(x: number, y: number): void {
    let maxX = Math.max(0, this.windowWidth / 2 - this.dialogW / 2)
    let maxY = Math.max(0, this.windowHeight / 2 - this.dialogH / 2)
    this.offsetX = Math.max(-maxX, Math.min(x, maxX))
    this.offsetY = Math.max(-maxY, Math.min(y, maxY))
  }

  build() {
    Column() {
      Text('可拖拽对话框')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)

      Text('拖动标题区域移动对话框')
        .fontSize(14)
        .fontColor('#999999')
        .margin({ top: 8 })

      Row() {
        Button('关闭')
          .onClick(() => {
            this.controller.close()
          })
      }
      .margin({ top: 20 })
      .justifyContent(FlexAlign.End)
      .width('100%')
    }
    .width(280)
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .shadow({
      radius: 10,
      color: '#888888',
      offsetX: 5,
      offsetY: 5
    })
    .onAreaChange((_oldArea: Area, newArea: Area) => {
      this.dialogW = Number(newArea.width)
      this.dialogH = Number(newArea.height)
    })
    .offset({ x: this.offsetX, y: this.offsetY })
    .gesture(
      PanGesture({ fingers: 1, distance: 5 })
        .onActionStart(() => {
          this.lastX = this.offsetX
          this.lastY = this.offsetY
        })
        .onActionUpdate((event: GestureEvent) => {
          this.clamp(this.lastX + event.offsetX, this.lastY + event.offsetY)
        })
        .onActionEnd((event: GestureEvent) => {
          this.clamp(this.lastX + event.offsetX, this.lastY + event.offsetY)
          this.lastX = this.offsetX
          this.lastY = this.offsetY
        })
    )
  }
}

@Entry
@Component
struct Index {
  @State windowWidth: number = 360
  @State windowHeight: number = 640
  dialogController: CustomDialogController = new CustomDialogController({
    builder: DraggableDialog({ windowWidth: this.windowWidth, windowHeight: this.windowHeight }),
    customStyle: true,
    alignment: DialogAlignment.Center,
    autoCancel: true
  })
  
  build() {
    Column() {
      Button('打开可拖拽对话框')
        .margin({ top: 20 })
        .onClick(() => {
          this.dialogController.open()
        })
    }
    .width('100%')
    .height('100%')
    .onAreaChange((_oldArea: Area, newArea: Area) => {
      this.windowWidth = Number(newArea.width)
      this.windowHeight = Number(newArea.height)
    })
  }
}

更多关于HarmonyOS 鸿蒙Next中如何实现弹窗整体拖动?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


@CustomDialog
struct DraggableCustomDialog {
        controller?: CustomDialogController
        // 拖拽偏移量
        @State offsetX: number = 0
        @State offsetY: number = 0
        // 记录上一次拖拽结束时的位置
        @State positionX: number = 0
        @State positionY: number = 0

        build() {
                Column() {
                        // 弹窗标题栏(拖拽热区)
                        Row() {
                                Text('可拖拽弹窗')
                                        .fontSize(18)
                                        .fontWeight(FontWeight.Bold)
                                        // .fontColor(Color.White)
                                        .layoutWeight(1)

                                Image($r('sys.media.ohos_ic_public_close'))
                                        .width(24)
                                        .height(24)
                                        .onClick(() => {
                                                this.controller?.close()
                                        })
                        }
                        .width('100%')
                        .padding({
                                left: 16,
                                right: 16,
                                top: 12,
                                bottom: 12
                        })

                        // 弹窗内容区
                        Column() {
                                Text('拖动标题栏即可移动弹窗位置')
                                        .fontSize(14)
                                        .margin({ top: 16, bottom: 24 })

                                Row() {
                                        Button('取消')
                                                .onClick(() => {
                                                        this.controller?.close()
                                                })
                                        Button('确定')
                                                .onClick(() => {
                                                        this.controller?.close()
                                                })
                                }
                                .width('100%')
                                .justifyContent(FlexAlign.SpaceEvenly)
                        }
                        .padding({ left: 16, right: 16, bottom: 16 })
                }
                .width(280)
                .borderRadius(12)
                // 通过 translate 实现位置偏移
                .translate({ x: this.offsetX, y: this.offsetY })
                // 绑定平移手势,实现拖拽
                .gesture(
                        PanGesture()
                                .onActionStart(() => {
                                        // 记录拖拽开始时的位置
                                        this.positionX = this.offsetX
                                        this.positionY = this.offsetY
                                })
                                .onActionUpdate((event: GestureEvent) => {
                                        // 实时更新偏移量:上次位置 + 本次拖拽偏移
                                        // this.offsetX = this.positionX + event.offsetX
                                        // this.offsetY = this.positionY + event.offsetY

                                        let newX = this.positionX + event.offsetX
                                        let newY = this.positionY + event.offsetY
                                        // 限制偏移范围,避免拖出屏幕(根据实际弹窗尺寸调整阈值)
                                        this.offsetX = Math.max(-200, Math.min(200, newX))
                                        this.offsetY = Math.max(-400, Math.min(400, newY))

                                })
                                .onActionEnd((event: GestureEvent) => {
                                        // 拖拽结束,保存最终位置
                                        this.positionX = this.positionX + event.offsetX
                                        this.positionY = this.positionY + event.offsetY
                                })
                )
                .shadow( ShadowStyle.OUTER_FLOATING_SM)
                .backgroundColor(Color.White)
        }
}

@Entry
@Component
struct Index {
        dialogController: CustomDialogController = new CustomDialogController({
                builder: DraggableCustomDialog(),
                customStyle: true, // 必须设为true,才能自定义弹窗样式
                alignment: DialogAlignment.Center,
                autoCancel: false,

        })

        build() {
                Column() {
                        Button('打开可拖拽弹窗')
                                .onClick(() => {
                                        this.dialogController.open()
                                })
                }
                .width('100%')
                .height('100%')
                .justifyContent(FlexAlign.Center)
        }
}

图片

https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-methods-custom-dialog-box#customdialogcontroller

通过CustomDialogController类显示自定义弹窗。使用弹窗组件时,优先考虑自定义弹窗,便于弹窗样式与内容的自定义。

CustomDialogController 本身没有“整窗拖拽”的开关,通常做法是在弹窗内容根容器上自己接 PanGesture,再用 offset/translate 控制整个内容的位置。

思路是:弹窗内部最外层用 Stack/Column 包住,维护 offsetX、offsetY;在标题栏或整个弹窗区域绑定 PanGesture,onActionUpdate 里累加位移,onActionEnd 时做边界吸附或限制不要拖出屏幕。CustomDialog 仍负责遮罩和显示/关闭,拖动只作用在你自己的弹窗内容上。如果后续还要做全屏转场或多页面切换,bindContentCover/NavDestination 会比 CustomDialog 更容易维护。

在HarmonyOS Next中,给弹窗根容器添加.gesture(PanGesture(options).onActionUpdate((event: GestureEvent) => { this.offsetX += event.offsetX; this.offsetY += event.offsetY; })),并通过@State offsetX: number = 0; @State offsetY: number = 0;记录偏移,容器使用.offset({ x: this.offsetX, y: this.offsetY })即可实现整体拖动。

  • CustomDialog 的弹窗本身不支持直接拖动,但可以在自定义弹窗的内容区域绑定拖动手势,通过偏移量实现整体拖动效果。核心思路:在弹窗的根布局组件上配置 PanGesture,根据手势位移更新 translate 偏移。

参考实现示例:

@CustomDialog
struct DraggableDialog {
  controller: CustomDialogController;
  @State offsetX: number = 0;
  @State offsetY: number = 0;

  build() {
    Column() {
      // 弹窗内容
      Text('可拖动的弹窗')
    }
    .width('80%')
    .height(200)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .translate({ x: this.offsetX, y: this.offsetY })
    .gesture(
      PanGesture({ direction: PanDirection.All })
        .onActionUpdate((event: GestureEvent) => {
          this.offsetX += event.offsetX;
          this.offsetY += event.offsetY;
        })
        .onActionEnd(() => {
          // 可在此处限制边界或回弹
        })
    )
  }
}

使用时注意:弹窗默认居中,拖动偏移量是相对原位置的累积增量,如需限制边界可在 onActionEnd 中根据窗口尺寸修正 offsetXoffsetY。该方案无需替换 CustomDialogController,仅在自定义内容中增加手势处理即可。

回到顶部