鸿蒙HarmonyOS选择面实战 —— ActionSheet、Sheet、Menu、Overlay 到底用哪个?
一、前言:为什么"弹什么"比"怎么弹"更重要在鸿蒙 ArkUI 里,能"弹出来"的能力至少有五个:bindMenu—— 锚定在按钮上的命令列表bindContextMenu—— 对象长按/右键的上下文命令UIContext.showActionSheet—— 系统托管的底部短决策面bindSheet—— 半模态自定义页面OverlayManager—— 应用自管浮层它们看起来都"弹出个东西",很多开发者就随手挑一个用。但真正上真机后,问题就来了:想做一个"分享 / 复制 / 移动"的三按钮底部选择面,用 ActionSheet,结果按钮行又小又挤。想在 ActionSheet 里放输入框和滚动列表,发现根本没有地方放自定义内容。用bindSheet弹出半屏表单,点击输入框后底部按钮被键盘死死挡住。把bindSheet挂在HdsNavDestination上,编译通过但运行时根本不弹。用 ActionSheet 做危险删除,想把按钮标红,发现DialogButtonStyle.ERROR根本不存在。这些问题背后都指向同一个根因:没有先选对"选择面类型",就直接跳到了 API 细节。本文基于 ArkUILab 项目 E008 实验,先把选择面的分层模型讲清楚,再逐个给出 ActionSheet 和 Sheet 的实战代码与踩坑修复过程。二、选择面分层模型:五个层级各管一件事E008 最核心的产出,是这张"选择面分层模型"。请先记住一句话:Menu、ContextMenu、ActionSheet、Sheet、Overlay 不是同一种"弹出层"的不同样式,而是选择交互的五个不同层级。HarmonyOS Selection Experience 分层 ┌─────────────────────────────────────────────────────────┐ │ Menu │ │ 解决:"我点了一个按钮,现在要执行哪个命令" │ │ 特征:锚定触发器、命令列表、轻量、非/模态可控 │ ├─────────────────────────────────────────────────────────┤ │ ContextMenu │ │ 解决:"我正在操作这个对象,它有哪些上下文命令" │ │ 特征:长按/右键、对象身份、preview、模态可选 │ ├─────────────────────────────────────────────────────────┤ │ ActionSheet │ │ 解决:"我需要从少量动作里做一个短决策,可取消/确认" │ │ 特征:系统托管、底部弹出、短标题 + 少量动作 │ ├─────────────────────────────────────────────────────────┤ │ Sheet │ │ 解决:"不离开当前页面,完成一个半模态任务" │ │ 特征:自定义内容、输入、滚动、拖拽、detents、dismiss │ ├─────────────────────────────────────────────────────────┤ │ Overlay │ │ 解决:"应用自管浮层,自定义手势和生命周期" │ │ 特征:OverlayManager、Page 之上 Dialog 之下、自管动画 │ └─────────────────────────────────────────────────────────┘对应的决策一句话:你要做的事用什么工具栏/标题栏的命令菜单Menu(bindMenu)长按卡片/文件的操作ContextMenu(bindContextMenu)底部弹出的"取消 / 确认 / 归档"短决策ActionSheet半屏表单、编辑、列表、详情Sheet(bindSheet)下拉快捷访问、自定义手势浮层Overlay(OverlayManager)最容易混淆的两条边界:ActionSheet ≠ 更短的 Sheet。ActionSheet 是系统托管的短决策面,内容结构固定(title/message/sheets/confirm/cancel),不能放自定义布局。需要自定义内容时直接上 Sheet。Sheet ≠ 更长的 ActionSheet。Sheet 是半模态"页面",有自己的生命周期、拖拽、detents、dismiss 拦截,承载的是一整套工作流,不是几个按钮。三、文档地图:选择面要横跨五篇文档和 E007 的 Menu 一样,选择面(Selection Experience)也没有"一篇文档讲完"。E008 总结的阅读顺序:Selection Experience 文档地图(按阅读顺序) ├── 1. Menu Guide + Universal Menu Attribute │ 先读,理解锚定命令和单值选择 │ ├── 2. ActionSheet Reference │ 需要"短决策/确认/取消"时读 │ 负责 UIContext.showActionSheet、ActionSheetOptions、SheetInfo │ 关键:它不是自定义内容容器 │ ├── 3. Sheet Page Guide │ 需要"半模态任务"时读 │ 负责 bindSheet、Bottom Sheet、Half Sheet、drag、lifecycle │ ├── 4. common.d.ts 里的 SheetOptions(Reference) │ 真正的 API 边界在这里 │ SheetSize、SheetType、SheetMode、detents、dismiss guard │ └── 5. OverlayManager Guide 需要自管浮层时读 Page 之上、Dialog 之下的应用自管层关键结论:Sheet 的 Guide 只建立心智模型,真正的能力边界(SheetSize、detents、SheetMode、keyboardAvoidMode、onWillDismiss)要回到common.d.ts的SheetOptionsReference 里查。四、ActionSheet 实战:系统托管的短决策面4.1 ActionSheet 适合什么?ActionSheet 是系统托管的短动作选择面,从底部弹出。它适合:少量动作的快速决策(比如"归档还是删除")明确的 cancel / confirm 语义危险操作的二次确认手机底部可达性优于小锚点菜单的场景它不适合:自定义按钮外形、高度、间距复杂命令布局(三四个大按钮纵向排列)输入、列表、滚动、编辑持久值选择(那是 Select 的职责)4.2 危险确认 ActionSheet:正确用法下面是 E008 Lab 里验证过的 ActionSheet 用法——一个删除确认场景:private ShowDangerActionSheet(): void { this.getUIContext().showActionSheet({ title: 'Delete roast profile?', message: 'ActionSheet is suitable for a compact destructive choice, but not for editing flows.', sheets: [ { title: 'Archive instead', action: () = { this.SelectAction('ActionSheet: Archive instead'); } } ], confirm: { value: 'Delete', style: DialogButtonStyle.HIGHLIGHT, action: () = { this.SelectAction('ActionSheet destructive confirm: Dele