弹窗交互:AlertDialog与CustomDialog的创建与关闭(11)
在鸿蒙 ArkUI 开发中,弹窗是处理用户交互反馈(如警告、确认、自定义提示)的核心组件。根据官方文档与最佳实践,ArkUI 提供了固定样式的AlertDialog和高度可定制的CustomDialog。
以下系统梳理这两种弹窗的创建、关闭及进阶封装方案。
一、 AlertDialog:轻量级警告弹窗
AlertDialog适用于简单的信息提示或需要用户进行“确认/取消”操作的场景。它由标题区、内容区和操作按钮区组成,无需开发者手动构建 UI 布局。
1. 基础用法(双按钮)
通过primaryButton和secondaryButton配置两个操作按钮,并通过action回调处理点击逻辑。
Button('点击显示警告弹窗') .onClick(() => { AlertDialog.show({ title: '删除联系人', message: '是否删除所选的联系人?', autoCancel: true, // 点击遮罩层时是否自动关闭 primaryButton: { value: '取消', action: () => { console.info('点击了取消'); } }, secondaryButton: { value: '删除', fontColor: Color.Red, // 危险操作通常标红 action: () => { console.info('成功删除'); } } }); })import { promptAction } from '@kit.ArkUI'; @Entry @Component struct AdvancedInputExample { @State username: string = ''; @State password: string = ''; @State searchKey: string = ''; build() { Column({ space: 20 }) { // 1. 账号输入框 TextInput({ placeholder: '请输入账号(仅限字母数字)', text: this.username }) .type(InputType.Normal) .maxLength(20) .inputFilter('[a-zA-Z0-9]') .onChange((value: string) => { this.username = value; }) .width('100%') .height(48) .backgroundColor('#F5F5F5') .borderRadius(8) .padding({ left: 15, right: 15 } as EdgeWidths) // 2. 密码输入框 TextInput({ placeholder: '请输入密码', text: this.password }) .type(InputType.Password) .maxLength(16) .caretColor('#007DFF') .placeholderColor('#CCCCCC') .onChange((value: string) => { this.password = value; }) .width('100%') .height(48) .backgroundColor('#F5F5F5') .borderRadius(8) .padding({ left: 15, right: 15 } as EdgeWidths) // 3. 搜索框 TextInput({ placeholder: '搜索商品或文章', text: this.searchKey }) .enterKeyType(EnterKeyType.Search) .onSubmit(() => { promptAction.showToast({ message: `开始搜索: ${this.searchKey}` }); }) .onBlur(() => { console.log('Search input lost focus'); }) .width('100%') .height(40) .backgroundColor('#EEEEEE') .borderRadius(20) .padding({ left: 15, right: 15 } as EdgeWidths) .fontSize(14) // 👇 【新增】警告弹窗按钮放在这里 Button('点击显示警告弹窗') .onClick(() => { AlertDialog.show({ title: '删除联系人', message: '是否删除所选的联系人?', autoCancel: true, // 点击遮罩层时是否自动关闭 primaryButton: { value: '取消', action: () => { console.info('点击了取消'); } }, secondaryButton: { value: '删除', fontColor: Color.Red, // 危险操作通常标红 action: () => { console.info('成功删除'); } } }); }) .width('100%') .height(48) .backgroundColor('#FF4D4F') .fontColor(Color.White) .borderRadius(8) } .padding(20) .width('100%') } }2. 单按钮确认弹窗
如果仅需提示用户且只有一个确认动作,可以使用confirm属性替代双按钮配置。
AlertDialog.show({ title: '提示', message: '您的网络已断开!', confirm: { value: '知道了', action: () => { console.info('用户确认'); } } });二、 CustomDialogController:基础自定义弹窗
当系统的固定样式无法满足需求(例如需要包含输入框、复杂图文排版)时,需使用CustomDialog。其核心是通过CustomDialogController来控制弹窗的生命周期。
1. 定义弹窗组件
使用@CustomDialog装饰器标记组件,并通过controller接收控制器实例以支持内部关闭。
@CustomDialog export struct CommonDialog { controller: CustomDialogController; @State inputValue: string = ''; build() { Column() { Text('请输入昵称').fontSize(18).fontWeight(FontWeight.Bold).margin({ top: 20, bottom: 10 }) TextInput({ placeholder: '请输入...', text: this.inputValue }) .height(40).borderRadius(8).backgroundColor('#F5F5F5') .onChange((value) => { this.inputValue = value; }) Row() { Button('取消').flex(1).backgroundColor('#F5F5F5').fontColor('#333333') .onClick(() => { this.controller.close(); }) Button('确认').flex(1).margin({ left: 12 }).backgroundColor('#007DFF') .onClick(() => { console.log('提交内容:', this.inputValue); this.controller.close(); }) }.width('100%').margin({ top: 20, bottom: 20 }) } .padding(20).backgroundColor(Color.White).borderRadius(12) } }2. 页面调用与关闭
在宿主页面中初始化CustomDialogController,将自定义组件传入builder参数,并通过.open()和.close()方法控制显隐。
dialogController: CustomDialogController = new CustomDialogController({ builder: CommonDialog(), alignment: DialogAlignment.Center, cancelable: true // 允许点击外部蒙层关闭 }); // 打开弹窗 this.dialogController.open();三、 进阶推荐:全局解耦的 openCustomDialog
1. CustomDialog 的双向数据通信(@Link)
在实际业务中,弹窗往往需要展示父页面的数据,并将用户在弹窗内的操作结果回传给父页面。官方推荐使用@Link装饰器实现父子状态同步。
// 自定义弹窗组件:接收外部传入的数据并回传 @CustomDialog export struct EditNameDialog { controller: CustomDialogController; @Link currentName: string; // 使用 @Link 实现双向绑定 build() { Column({ space: 20 }) { Text('修改昵称').fontSize(18).fontWeight(FontWeight.Bold) TextInput({ text: this.currentName }) .onChange((value: string) => { this.currentName = value; }) Button('保存') .onClick(() => { // 点击保存时,父页面的 @State 变量会自动更新为最新值 this.controller.close(); }) } .padding(20).backgroundColor(Color.White).borderRadius(12) } } // 父页面调用示例 @Entry @Component struct ProfilePage { @State userName: string = 'HarmonyOS_Dev'; dialogController: CustomDialogController = new CustomDialogController({ builder: EditNameDialog({ currentName: $userName }), // 使用 $ 传递引用 alignment: DialogAlignment.Center, customStyle: true }); build() { Column() { Text(`当前昵称: ${this.userName}`) Button('编辑昵称').onClick(() => { this.dialogController.open(); }) } } }2. 高阶封装:开箱即用的通用业务弹窗 (CommonDialog)
为了避免每次遇到“标题+内容+双按钮”的需求都重写一遍CustomDialog,可以封装一个完全受控的通用组件,通过参数配置即可生成不同样式的弹窗。
// 定义统一的弹窗配置接口 export interface CommonDialogOptions { title?: string; content?: string; cancelText?: string; confirmText?: string; onCancel?: () => void; onConfirm?: () => void; } // 通用弹窗 UI 组件 @CustomDialog export struct CommonDialog { controller: CustomDialogController; options: CommonDialogOptions; build() { Column() { if (this.options.title) { Text(this.options.title).fontSize(18).fontWeight(FontWeight.Bold).margin({ top: 20, bottom: 10 }) } if (this.options.content) { Text(this.options.content).fontSize(14).fontColor('#666666').margin({ bottom: 20 }).textAlign(TextAlign.Center) } Row({ space: 12 }) { Button(this.options.cancelText || '取消') .flex(1).height(44).backgroundColor('#F5F5F5').fontColor('#333333') .onClick(() => { this.controller.close(); this.options.onCancel?.(); }) Button(this.options.confirmText || '确认') .flex(1).height(44).backgroundColor('#007DFF').fontColor(Color.White) .onClick(() => { this.controller.close(); this.options.onConfirm?.(); }) }.width('100%').margin({ bottom: 20 }) } .width('100%').padding({ left: 20, right: 20 }).backgroundColor(Color.White).borderRadius(12) } }3. 蒙层控制与高级交互
在某些复杂场景下,我们需要对弹窗的背景蒙层进行精细控制,例如允许点击蒙层穿透到下层页面,或者自定义蒙层的颜色。
- 非模态弹窗(背景可交互):将
isModal设置为false,此时弹窗不会阻断底层页面的滑动或点击事件。 - 自定义蒙层颜色:通过
maskColor属性调整遮罩透明度,常用于引导页或夜间模式。
Button('显示非模态提示') .onClick(() => { AlertDialog.show({ title: '系统升级中', message: '请在后台等待,您可以继续浏览其他页面。', isModal: false, // 【关键】设为 false,允许用户与弹窗背后的页面交互 maskColor: '0x00000000', // 隐藏蒙层 confirm: { value: '知道了' } }); })4. 生命周期回调与埋点监控
在大型项目中,我们通常需要知道弹窗何时真正渲染完成以触发埋点上报,或者在关闭后执行清理逻辑。ArkUI 提供了完整的时序回调支持:
AlertDialog.show({ title: '广告位', message: '限时优惠...', confirm: { value: '立即查看' }, onWillAppear: () => console.info('弹窗即将出现(动效前)'), onDidAppear: () => { console.info('弹窗已完全展示'); // 【业务场景】在这里触发曝光埋点上报 }, onWillDisappear: () => console.info('弹窗即将消失(动效前)'), onDidDisappear: () => { console.info('弹窗已彻底销毁'); // 【业务场景】在这里释放相关的定时器或监听器 } });虽然CustomDialogController易于上手,但它在实际工程中存在诸多限制(不支持动态创建、刷新受限)。官方强烈推荐使用从UIContext获取的PromptAction对象提供的openCustomDialogAPI 来实现完全解耦的全局弹窗。
核心优势
- 与 UI 解耦:通过
ComponentContent封装内容,无需在每个页面都绑定 Controller。 - 支持动态更新:弹窗打开后,可通过
updateCustomDialog动态修改对齐方式、偏移量或蒙层颜色等属性。 - 完善的生命周期:提供
onWillAppear、onDidAppear、onWillDisappear、onDidDisappear四个时序回调,方便做埋点或动画过渡。
最佳实践:封装静态工具类
为了避免在业务代码中重复编写冗长的ComponentContent创建与销毁逻辑,建议封装一个统一的DialogController工具类:
import { promptAction } from '@kit.ArkUI'; export class GlobalDialogUtil { private static dialogNode: ComponentContent<Object> | null = null; // 打开全局自定义弹窗 static show(builder: WrappedBuilder<[Object]>, params?: Object) { this.dismiss(); // 防止重复弹出 const ctx = getContext(this); this.dialogNode = new ComponentContent(ctx, builder, params); promptAction.openCustomDialog(this.dialogNode, { isModal: true, autoCancel: true, alignment: DialogAlignment.Center, maskColor: '0x33000000' }).then(() => { console.info('Global Dialog opened.'); }); } // 关闭并释放资源 static dismiss() { if (this.dialogNode) { promptAction.closeCustomDialog(this.dialogNode).then(() => { this.dialogNode?.dispose(); // 【关键】必须手动释放内存 this.dialogNode = null; }); } } }💡 选型总结建议
- 简单提示/确认:直接使用
AlertDialog.show(),零成本接入。 - 常规业务自定义弹窗:使用
CustomDialogController。 - 全局通用弹窗/复杂交互/需要动态更新样式:务必采用
PromptAction.openCustomDialog+ComponentContent模式,这是目前鸿蒙应用框架下最优雅、扩展性最强的弹窗解决方案。
