一、前言

前文使用的 AlertDialog 样式固定,仅能配置标题、文本、按钮,无法满足头像选择、表单弹窗、确认弹窗、底部弹窗等个性化界面需求。OpenHarmony 提供 CustomDialog 自定义弹窗组件,基于 ArkUI 自由布局,支持自定义样式、交互、尺寸、动画,是项目中高频使用的进阶组件。

本文讲解自定义弹窗定义、调用传参、弹窗样式、关闭逻辑、嵌套交互,搭配多个可直接运行的案例,并结合全局状态完成综合实战。

二、核心基础说明

  1. 定义规则:使用 @CustomDialog 装饰器声明自定义弹窗,区别于普通页面组件 @Component
  2. 弹窗参数:通过 @DialogParam 接收外部传入参数、回调函数,实现父子通信。
  3. 关闭弹窗:组件内置 controller.close() 方法,主动关闭弹窗。
  4. 弹窗控制器CustomDialogController 用于创建、唤起、控制弹窗。

三、基础用法:简单提示弹窗

3.1 基础自定义弹窗(纯提示 + 双按钮)

ets

// 1. 定义自定义弹窗
@CustomDialog
struct SimpleDialog {
  // 弹窗控制器,内置方法:close()
  controller: CustomDialogController
  // 接收外部回调
  @DialogParam confirmCallback: () => void
  @DialogParam cancelCallback: () => void

  build() {
    Column({ space: 20 }) {
      // 弹窗标题
      Text("操作提示")
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      // 内容
      Text("确定执行当前操作吗?").fontSize(16)

      // 按钮区域
      Row({ space: 30 }) {
        Button("取消")
          .width(100)
          .backgroundColor("#999")
          .onClick(() => {
            this.controller.close() // 关闭弹窗
            this.cancelCallback()   // 执行取消回调
          })

        Button("确定")
          .width(100)
          .backgroundColor("#007DFF")
          .onClick(() => {
            this.controller.close()
            this.confirmCallback()  // 执行确定回调
          })
      }
    }
    .width(300)
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }
}

// 2. 页面入口,调用弹窗
@Entry
@Component
struct DialogIndex {
  // 创建弹窗控制器
  dialogController: CustomDialogController = new CustomDialogController({
    builder: SimpleDialog({
      confirmCallback: () => {
        console.log("点击了确定按钮")
      },
      cancelCallback: () => {
        console.log("点击了取消按钮")
      }
    }),
    // 弹窗配置:点击遮罩是否关闭弹窗
    autoCancel: true
  })

  build() {
    Column() {
      Button("打开自定义提示弹窗")
        .width(220)
        .onClick(() => {
          this.dialogController.open() // 唤起弹窗
        })
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

3.2 配置项说明

CustomDialogController 中可配置常用属性:

  • autoCancel:布尔值,点击弹窗外部遮罩层是否关闭弹窗,默认 true
  • alignment:弹窗对齐方式(居中、底部、顶部、左侧、右侧)
  • offset:弹窗偏移距离
  • maskColor:遮罩层颜色

四、进阶案例 1:带输入框的表单弹窗

适用于弹窗内输入内容、评论、备注、修改昵称等场景,实现弹窗内数据收集。

ets

@CustomDialog
struct InputDialog {
  controller: CustomDialogController
  // 回传给父页面的输入内容
  @DialogParam submit: (content: string) => void
  @State inputText: string = ""

  build() {
    Column({ space: 18 }) {
      Text("请输入备注信息").fontSize(18).fontWeight(FontWeight.Bold)
      // 弹窗内输入框
      TextInput({ placeholder: "在此输入内容", text: this.inputText })
        .width("100%")
        .height(45)
        .onChange(val => this.inputText = val)

      Row({ space: 20 }) {
        Button("关闭")
          .layoutWeight(1)
          .backgroundColor("#EEEEEE")
          .fontColor("#333")
          .onClick(() => this.controller.close())

        Button("提交")
          .layoutWeight(1)
          .backgroundColor("#007DFF")
          .onClick(() => {
            this.submit(this.inputText)
            this.controller.close()
          })
      }
    }
    .width(320)
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(10)
  }
}

@Entry
@Component
struct InputDialogPage {
  @State result: string = ""
  inputDialog: CustomDialogController = new CustomDialogController({
    builder: InputDialog({
      submit: (val: string) => {
        this.result = "弹窗输入内容:" + val
      }
    }),
    autoCancel: false // 禁止点击遮罩关闭
  })

  build() {
    Column({ space: 20 }) {
      Button("打开输入弹窗")
        .width(220)
        .onClick(() => this.inputDialog.open())
      Text(this.result).fontSize(18)
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
    .padding(20)
  }
}

五、进阶案例 2:底部弹窗(底部弹出菜单)

通过 alignment 实现从页面底部弹出的菜单弹窗,常用于分享、操作菜单。

ets

@CustomDialog
struct BottomMenuDialog {
  controller: CustomDialogController
  @DialogParam menuClick: (index: number) => void

  build() {
    Column() {
      List({ space: 1 }) {
        ListItem() {
          Text("分享好友")
            .width("100%")
            .height(50)
            .textAlign(TextAlign.Center)
            .fontSize(17)
            .onClick(() => {
              this.menuClick(1)
              this.controller.close()
            })
        }
        ListItem() {
          Text("收藏内容")
            .width("100%")
            .height(50)
            .textAlign(TextAlign.Center)
            .fontSize(17)
            .onClick(() => {
              this.menuClick(2)
              this.controller.close()
            })
        }
        ListItem() {
          Text("举报")
            .width("100%")
            .height(50)
            .textAlign(TextAlign.Center)
            .fontSize(17)
            .onClick(() => {
              this.menuClick(3)
              this.controller.close()
            })
        }
      }
      .width("100%")

      // 取消按钮
      Button("取消")
        .width("100%")
        .height(50)
        .margin({ top: 10 })
        .backgroundColor(Color.White)
        .onClick(() => this.controller.close())
    }
    .width("100%")
    .backgroundColor("#F5F5F5")
  }
}

@Entry
@Component
struct BottomDialogPage {
  @State tip: string = ""
  bottomDialog: CustomDialogController = new CustomDialogController({
    builder: BottomMenuDialog({
      menuClick: (idx: number) => {
        switch (idx) {
          case 1:
            this.tip = "已选择:分享好友"
            break
          case 2:
            this.tip = "已选择:收藏内容"
            break
          case 3:
            this.tip = "已选择:举报"
            break
        }
      }
    }),
    alignment: DialogAlignment.Bottom, // 弹窗底部对齐
    autoCancel: true
  })

  build() {
    Column({ space: 20 }) {
      Button("打开底部菜单弹窗")
        .width(220)
        .onClick(() => this.bottomDialog.open())
      Text(this.tip).fontSize(18)
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

六、综合实战:弹窗修改全局用户名(联动 AppStorage)

结合之前的全局状态 AppStorage,实现弹窗修改用户名 → 全局数据同步刷新完整业务。

ets

// 自定义修改昵称弹窗
@CustomDialog
struct EditNameDialog {
  controller: CustomDialogController
  // 接收父页面传入的原始名称
  @DialogParam oldName: string
  @DialogParam saveAction: (newName: string) => void
  @State tempName: string = ""

  aboutToAppear() {
    // 弹窗打开时赋值初始值
    this.tempName = this.oldName
  }

  build() {
    Column({ space: 15 }) {
      Text("修改用户名").fontSize(20).fontWeight(FontWeight.Bold)
      TextInput({ text: this.tempName })
        .width("100%")
        .height(45)
        .onChange(val => this.tempName = val)

      Row({ space: 20 }) {
        Button("取消")
          .layoutWeight(1)
          .onClick(() => this.controller.close())
        Button("保存")
          .layoutWeight(1)
          .backgroundColor("#007DFF")
          .onClick(() => {
            this.saveAction(this.tempName)
            this.controller.close()
          })
      }
    }
    .width(300)
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }
}

@Entry
@Component
struct UserInfoPage {
  // 双向绑定全局用户名
  @StorageLink("userName") userName: string
  editDialog: CustomDialogController = new CustomDialogController({
    builder: EditNameDialog({
      oldName: this.userName,
      saveAction: (newName: string) => {
        // 修改全局数据,所有绑定页面自动刷新
        this.userName = newName
      }
    })
  })

  build() {
    Column({ space: 25 }) {
      Text("当前用户名:" + this.userName).fontSize(20)
      Button("点击修改用户名")
        .width(220)
        .onClick(() => this.editDialog.open())
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
    .padding(20)
  }
}

七、开发要点、踩坑与最佳实践

7.1 核心使用规则

  1. 装饰器区分:
    • @CustomDialog:仅用于定义弹窗,不能作为独立页面
    • @Component:普通组件 / 页面
  2. 弹窗内部必须依赖 controller.close() 关闭,无法直接路由关闭。
  3. 外部传参、回调统一使用 @DialogParam,不建议直接使用 @Prop

7.2 常用配置技巧

  • 居中弹窗(默认):无需设置 alignment
  • 底部弹窗:alignment: DialogAlignment.Bottom
  • 禁止点击遮罩关闭:autoCancel: false(表单、重要确认弹窗推荐开启)
  • 自定义遮罩:maskColor: "rgba(0,0,0,0.6)"

7.3 常见问题解决

  1. 弹窗多次创建报错 控制器建议定义在组件顶层,不要在 onClick 内部重复 new
  2. 弹窗输入框数据不回显 在弹窗 aboutToAppear 生命周期中接收初始值,不要直接赋值。
  3. 弹窗尺寸适配 固定宽高适配小弹窗,百分比宽高适配底部全屏菜单。

7.4 项目规范

  1. 按业务拆分弹窗:提示弹窗、表单弹窗、菜单弹窗,单独文件管理。
  2. 重要操作弹窗(退出账号、删除数据)设置 autoCancel: false,防止误触。
  3. 弹窗样式统一:圆角、内边距、配色保持项目 UI 风格一致。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐