弹窗交互:AlertDialog与CustomDialog的创建与关闭(11)

在鸿蒙 ArkUI 开发中,弹窗是处理用户交互反馈(如警告、确认、自定义提示)的核心组件。根据官方文档与最佳实践,ArkUI 提供了固定样式的 AlertDialog 和高度可定制的 CustomDialog

以下系统梳理这两种弹窗的创建、关闭及进阶封装方案。

一、 AlertDialog:轻量级警告弹窗

AlertDialog 适用于简单的信息提示或需要用户进行"确认/取消"操作的场景。它由标题区、内容区和操作按钮区组成,无需开发者手动构建 UI 布局。

1. 基础用法(双按钮)

通过 primaryButtonsecondaryButton 配置两个操作按钮,并通过 action 回调处理点击逻辑。

TypeScript 复制代码
Button('点击显示警告弹窗')
  .onClick(() => {
    AlertDialog.show({
      title: '删除联系人',
      message: '是否删除所选的联系人?',
      autoCancel: true, // 点击遮罩层时是否自动关闭
      primaryButton: {
        value: '取消',
        action: () => { console.info('点击了取消'); }
      },
      secondaryButton: {
        value: '删除',
        fontColor: Color.Red, // 危险操作通常标红
        action: () => { console.info('成功删除'); }
      }
    });
  })
TypeScript 复制代码
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 属性替代双按钮配置。

TypeScript 复制代码
AlertDialog.show({
  title: '提示',
  message: '您的网络已断开!',
  confirm: {
    value: '知道了',
    action: () => { console.info('用户确认'); }
  }
});

二、 CustomDialogController:基础自定义弹窗

当系统的固定样式无法满足需求(例如需要包含输入框、复杂图文排版)时,需使用 CustomDialog。其核心是通过 CustomDialogController 来控制弹窗的生命周期。

1. 定义弹窗组件

使用 @CustomDialog 装饰器标记组件,并通过 controller 接收控制器实例以支持内部关闭。

TypeScript 复制代码
@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() 方法控制显隐。

TypeScript 复制代码
dialogController: CustomDialogController = new CustomDialogController({
  builder: CommonDialog(),
  alignment: DialogAlignment.Center,
  cancelable: true // 允许点击外部蒙层关闭
});

// 打开弹窗
this.dialogController.open();

三、 进阶推荐:全局解耦的 openCustomDialog

1. CustomDialog 的双向数据通信(@Link)

在实际业务中,弹窗往往需要展示父页面的数据,并将用户在弹窗内的操作结果回传给父页面。官方推荐使用 @Link 装饰器实现父子状态同步。

TypeScript 复制代码
// 自定义弹窗组件:接收外部传入的数据并回传
@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,可以封装一个完全受控的通用组件,通过参数配置即可生成不同样式的弹窗。

TypeScript 复制代码
// 定义统一的弹窗配置接口
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 属性调整遮罩透明度,常用于引导页或夜间模式。
TypeScript 复制代码
Button('显示非模态提示')
  .onClick(() => {
    AlertDialog.show({
      title: '系统升级中',
      message: '请在后台等待,您可以继续浏览其他页面。',
      isModal: false, // 【关键】设为 false,允许用户与弹窗背后的页面交互
      maskColor: '0x00000000', // 隐藏蒙层
      confirm: { value: '知道了' }
    });
  })
4. 生命周期回调与埋点监控

在大型项目中,我们通常需要知道弹窗何时真正渲染完成以触发埋点上报,或者在关闭后执行清理逻辑。ArkUI 提供了完整的时序回调支持:

TypeScript 复制代码
AlertDialog.show({
  title: '广告位',
  message: '限时优惠...',
  confirm: { value: '立即查看' },
  onWillAppear: () => console.info('弹窗即将出现(动效前)'),
  onDidAppear: () => {
    console.info('弹窗已完全展示');
    // 【业务场景】在这里触发曝光埋点上报
  },
  onWillDisappear: () => console.info('弹窗即将消失(动效前)'),
  onDidDisappear: () => {
    console.info('弹窗已彻底销毁');
    // 【业务场景】在这里释放相关的定时器或监听器
  }
});

虽然 CustomDialogController 易于上手,但它在实际工程中存在诸多限制(不支持动态创建、刷新受限)。官方强烈推荐 使用从 UIContext 获取的 PromptAction 对象提供的 openCustomDialog API 来实现完全解耦的全局弹窗。

核心优势
  • 与 UI 解耦 :通过 ComponentContent 封装内容,无需在每个页面都绑定 Controller。
  • 支持动态更新 :弹窗打开后,可通过 updateCustomDialog 动态修改对齐方式、偏移量或蒙层颜色等属性。
  • 完善的生命周期 :提供 onWillAppearonDidAppearonWillDisappearonDidDisappear 四个时序回调,方便做埋点或动画过渡。
最佳实践:封装静态工具类

为了避免在业务代码中重复编写冗长的 ComponentContent 创建与销毁逻辑,建议封装一个统一的 DialogController 工具类:

TypeScript 复制代码
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 模式,这是目前鸿蒙应用框架下最优雅、扩展性最强的弹窗解决方案。
相关推荐
90后的晨仔2 小时前
HarmonyOS 锁屏音频播放完整实践指南
harmonyos
90后的晨仔2 小时前
鸿蒙应用动态桌面图标功能实现完全指南
harmonyos
nashane2 小时前
HarmonyOS 6学习:JsCrash“闪退”法医指南——从FaultLog堆栈还原崩溃现场的终极手册
学习·华为·harmonyos
李二。3 小时前
鸿蒙OS NEXT 批量重命名工具:PC端文件管理的效率革命
华为·harmonyos
HwJack204 小时前
鸿蒙背景下 Cocos Creator 的三大 JS 引擎:JIT 与热更新的十字路口
javascript·华为·harmonyos
提子拌饭1334 小时前
Column 嵌套布局:多级 Column 实现复杂纵向结构——鸿蒙 HarmonyOS ArkTS 原生学习应用
学习·华为·harmonyos·鸿蒙·鸿蒙系统
前端不太难6 小时前
鸿蒙 App 分布式数据同步:架构设计 + Demo 实现
分布式·状态模式·harmonyos
美彦喷雾设备7 小时前
市面上正规的雾森系统厂家哪家可靠
鸿蒙系统