一、前言
前文使用的AlertDialog样式固定,仅能配置标题、文本、按钮,无法满足头像选择、表单弹窗、确认弹窗、底部弹窗等个性化界面需求。OpenHarmony 提供CustomDialog自定义弹窗组件,基于 ArkUI 自由布局,支持自定义样式、交互、尺寸、动画,是项目中高频使用的进阶组件。
本文讲解自定义弹窗定义、调用传参、弹窗样式、关闭逻辑、嵌套交互,搭配多个可直接运行的案例,并结合全局状态完成综合实战。
二、核心基础说明
- 定义规则:使用
@CustomDialog装饰器声明自定义弹窗,区别于普通页面组件@Component。 - 弹窗参数:通过
@DialogParam接收外部传入参数、回调函数,实现父子通信。 - 关闭弹窗:组件内置
controller.close()方法,主动关闭弹窗。 - 弹窗控制器:
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:布尔值,点击弹窗外部遮罩层是否关闭弹窗,默认truealignment:弹窗对齐方式(居中、底部、顶部、左侧、右侧)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 核心使用规则
- 装饰器区分:
@CustomDialog:仅用于定义弹窗,不能作为独立页面@Component:普通组件 / 页面
- 弹窗内部必须依赖
controller.close()关闭,无法直接路由关闭。 - 外部传参、回调统一使用
@DialogParam,不建议直接使用@Prop。
7.2 常用配置技巧
- 居中弹窗(默认):无需设置
alignment - 底部弹窗:
alignment: DialogAlignment.Bottom - 禁止点击遮罩关闭:
autoCancel: false(表单、重要确认弹窗推荐开启) - 自定义遮罩:
maskColor: "rgba(0,0,0,0.6)"
7.3 常见问题解决
- 弹窗多次创建报错控制器建议定义在组件顶层,不要在
onClick内部重复new。 - 弹窗输入框数据不回显在弹窗
aboutToAppear生命周期中接收初始值,不要直接赋值。 - 弹窗尺寸适配固定宽高适配小弹窗,百分比宽高适配底部全屏菜单。
7.4 项目规范
- 按业务拆分弹窗:提示弹窗、表单弹窗、菜单弹窗,单独文件管理。
- 重要操作弹窗(退出账号、删除数据)设置
autoCancel: false,防止误触。 - 弹窗样式统一:圆角、内边距、配色保持项目 UI 风格一致。