鸿蒙开发中的弹窗方案对比

199 阅读6分钟

前言

大家好,我是simple。我的理想是利用科技手段来解决生活中遇到的各种问题

在鸿蒙应用开发里,弹窗是很常用的交互组件。不同的弹窗方案各有各的特点,选对了方案能让开发效率提高不少,用户体验也会更好。今天就来聊聊几种常见的弹窗方案。

一、CustomDialog:定制化弹窗的标准方案

CustomDialog 是鸿蒙官方提供的自定义弹窗组件,用装饰器声明弹窗内容,再通过控制器来管理弹窗的显示和隐藏,比较适合那些需要规范交互流程的场景。

关键代码示例(调用方式)

// 1. 定义弹窗内容(@CustomDialog装饰的组件)
@CustomDialog
struct MyDialog { ... }
// 2. 创建控制器
private dialogController = new CustomDialogController({
  builder: MyDialog({ /* 传递参数 */ }),
  alignment: DialogAlignment.Center
})
// 3. 调用显示
Button("打开弹窗").onClick(() => this.dialogController.open())

优势

  1. 有框架自带的弹窗特性,比如遮罩、动画和层级管理,不用自己操心
  1. 生命周期很明确,创建、显示、销毁都有固定流程
  1. 可以设置位置、大小这些属性,配置还算灵活
  1. 参数传递是结构化的,不容易出错

劣势

  1. 代码有点啰嗦,既要写弹窗组件,又要创建控制器
  1. 控制器和弹窗组件绑得比较紧,想换个弹窗样式不容易
  1. 要做不同样式的弹窗,得创建好多个组件,复用起来麻烦

适用场景

中等复杂度的交互弹窗,像表单确认、信息展示这些场景就很合适,尤其是需要统一交互规范的时候。

二、BindSheet:底部滑出式交互面板

BindSheet 是专门从底部滑出来的弹窗组件,自带滑入滑出的动画,位置固定在底部,很适合做操作菜单或者功能入口。

关键代码示例(调用方式)

// 1. 用状态变量控制显示
@State showSheet: boolean = false

// 2. 在布局里声明BindSheet
Row()
.bindSheet($$this.showSheet, this.showSheetBuilder())

// 3. 点击按钮切换状态
Button("打开底部面板").onClick(() => this.showSheet = true)

优势

  1. 自带平滑的动画效果,滑入滑出很自然
  1. 固定从底部弹出,用户对这种交互比较熟悉
  1. 用法简单,用一个状态变量就能控制显示和隐藏
  1. 很适合做操作菜单,比如分享面板、筛选选项这些

劣势

  1. 样式太固定了,位置和动画都改不了
  1. 层级是固定的,总会在最底部的弹窗层级
  1. 功能比较单一,只能从底部弹出来,做不了太复杂的交互
  1. 每次加载都会重新运行一次aboutToAppear,且无法缓存

适用场景

像分享面板、操作菜单、筛选条件选择这些需要从底部唤起的轻量交互场景,用它就很合适。

三、promptAction.showDialog ():快捷式系统弹窗

promptAction 里的 showDialog 方法是鸿蒙封装好的快捷弹窗 API,调用起来特别方便,适合快速实现一些简单的交互。

关键代码示例(调用方式)

// 直接调用showDialog方法,传入配置和按钮
Button("打开系统弹窗").onClick(async () => {
  const result = await promptAction.showDialog({
    title: "提示",
    message: "确定要这么做吗?",
    buttons: [/* 按钮配置 */]
  })
  // 根据用户点击的按钮索引做相应处理
  if (result.index === 0) { /* 处理逻辑 */ }
})

优势

  1. 调用特别简单,一行代码就能弹出弹窗,不用提前定义组件
  1. 支持 async/await 语法,处理用户操作后的逻辑很方便
  1. 样式和系统主题保持一致,看起来很协调
  1. 很轻量,占用资源少,性能开销小

劣势

  1. 样式太固定了,没法自定义 UI,只能显示文本和按钮
  1. 功能有限,复杂的布局和交互组件都加不进去
  1. 想加动画或者特殊效果基本不可能

适用场景

简单的确认提示、快速的操作反馈,不需要定制 UI 的基础交互场景,用它准没错。

四、PromptManager:业务与 UI 分离的进阶方案

PromptManager 是基于鸿蒙 API 封装的弹窗管理方案,最大的特点是把 UI 展示和业务逻辑分开了,维护起来更方便,复用性也高。

核心实现:open 和 close 流程

open 方法流程

  1. 先获取当前窗口的 UI 上下文,这是打开弹窗的基础
  1. 通过 UI 上下文拿到弹窗操作对象(promptAction)
  1. 调用 openCustomDialog 方法打开弹窗,同时设置弹窗的位置等参数
  1. 生成一个唯一的 UUID,把弹窗实例存到 Map 里,方便后续关闭
  1. 返回这个 UUID,供关闭时使用
async open(contentNode: ComponentContent<object>): Promise<string> {
  // 获取UI上下文和弹窗操作对象
  const uiContext = await this.getUiContext()
  const customPrompt = uiContext.getPromptAction()
  
  // 打开弹窗并设置参数
  customPrompt.openCustomDialog(contentNode, {
    alignment: DialogAlignment.Center,
    autoCancel: false
  })
  
  // 生成UUID并存储弹窗实例
  const uuid = util.generateRandomUUID()
  this.map.set(uuid, contentNode)
  
  return uuid
}

close 方法流程

  1. 接收要关闭的弹窗 UUID
  1. 从 Map 里查找对应的弹窗实例
  1. 如果找到,调用 closeCustomDialog 方法关闭弹窗
  1. 从 Map 里删除这个弹窗实例,释放资源
async close(uuid: string) {
  // 查找弹窗实例
  if (this.map.has(uuid)) {
    const customPrompt = await this.getPromptAction()
    // 关闭弹窗
    customPrompt.closeCustomDialog(this.map.get(uuid))
    // 移除实例
    this.map.delete(uuid)
  }
}

关键代码示例(使用方式)

// 1. 定义纯UI的弹窗内容
function SharePanel(params: { onShare: (type: string) => void, close: () => void }) {
  // 只负责展示UI,点击事件通过参数回调给业务层
}
// 2. 业务层调用
Button("打开分享弹窗").onClick(async () => {
  let uuid = ""
  // 创建内容节点,绑定UI和业务回调
  const contentNode = new ComponentContent(...)
  // 打开弹窗并获取UUID
  uuid = await promptManager.open(contentNode)
})

优势

  1. UI 和业务逻辑完全分开,各改各的不影响
  1. 同一个 UI 组件能在不同业务场景里复用
  1. 弹窗的生命周期由管理器统一管理,不容易出问题
  1. 灵活度高,想换 UI 或者改业务逻辑都很方便

劣势

  1. 一开始要封装这个管理器,前期开发成本有点高
  1. 团队里的人得先理解这种分离的思路才能用好
  1. UI 和业务之间的参数接口得定义清楚,不然容易出错

适用场景

中大型项目、需要长期维护的应用、UI 和业务逻辑经常变动的场景,还有多团队协作开发的时候,用这个方案会比较省心。

方案对比与选择建议

评估维度CustomDialogBindSheetshowDialog()PromptManager
开发效率极高低(初期)/ 高(后期)
UI 定制能力中(样式固定)极高
业务耦合度
复用性
维护成本低(简单场景)低(复杂场景)

实际开发里,也可以把这些方案结合起来用。比如简单的提示用 showDialog,底部菜单用 BindSheet,核心业务弹窗用 PromptManager,这样搭配着来既高效又灵活。