灶台菜谱设计手记 04|HarmonyOS 7.0 空间化:信息折叠展开【鸿蒙心迹】

1. 前言
番茄炖牛腩正在小火炖煮。用户想确认盐的用量,于是从“烹饪”切到“备菜”。查完以后,他需要回到刚才的步骤。
这是一段很短的导航,却包含两种不同的变化:正在阅读的页面变了,正在执行的烹饪步骤没有变。如果界面只记得前者,用户便需要自己记住后者。对于一个放在灶台旁、经常被打断阅读的应用,这种要求并不合理。
前三篇分别讨论了材质的生效区域、文字的可读性和操作反馈。本篇继续使用同一份番茄炖牛腩菜谱,以及“备菜、烹饪、笔记”三个页签。我想解决的问题是:怎样让当前步骤的入口持续可见,同时保留足够的正文阅读空间?
将摘要放进底部MiniBar是一个自然的选择。用户翻看配料时,仍能看到“当前步骤:小火炖煮”。但这个选择也带来另一项责任:悬浮区域可能挡住列表末行。入口变得容易找,配料却读不全,任务仍然没有完成。

图04-D:左侧标出切换到备菜后的步骤入口,右侧标出滚动到底后的阅读边界。盐的用量和末行内容分别读取原菜谱,不调整食材顺序来迁就构图。
我会把这两个问题放在一起处理。统一入口负责维持任务上下文,正文避让负责保留阅读空间。两者都需要经历窗口变化和大字体的检查。
2. 方案
先不急着配置组件。我们有两种可以比较的安排。
方案A在各页面放置“返回当前步骤”按钮。它容易融入已有布局,但位置可能随页面结构改变。方案B将摘要和返回入口集中到MiniBar中,让它们跨页签保持一致。它减少了入口位置的变化,却引入了悬浮区域与正文重叠的问题。
| 比较项 | 方案A:页面内入口 | 方案B:统一MiniBar |
|---|---|---|
| 返回位置 | 由各页面安排 | 由公共布局承载 |
| 当前步骤提示 | 各页面分别呈现 | 读取同一份步骤状态 |
| 空间代价 | 占用页面布局空间 | 需要检查悬浮覆盖范围 |
| 关键检查 | 入口是否容易找到 | 入口与末行是否同时可用 |
我倾向于方案B,因为返回当前步骤是一项跨页面的任务。不过,这个选择需要用完整操作路径评价。比较时固定菜谱、初始步骤、字号、背景和材质策略,记录操作次数、寻找入口的情况与内容遮挡。不能一边换布局,一边放大文字,再把所有变化归功于MiniBar。
接下来决定哪些信息值得长期占据这块空间。
| 信息 | 折叠时 | 展开时 |
|---|---|---|
| 当前步骤 | 保留可辨认的步骤摘要 | 增加序号及简短说明 |
| 返回入口 | 始终保留 | 保持相同含义 |
| 详细操作 | 在烹饪正文完整阅读 | 可补充摘要,正文仍保留全文 |
| 上一步完成标记 | 保留在烹饪页 | 空间允许时同步展示 |
| “下一步” | 留在烹饪页操作区 | 仍留在相同操作区 |
这里有意把“返回”与“下一步”分开。前者改变阅读位置,后者改变业务进度。在狭窄区域中并排放置两个含义不同的按钮,会增加解释和误操作的成本。本篇的MiniBar只负责摘要与返回。
折叠也不能删除恢复任务所需的信息。若大字体挤压了空间,先减少可选说明,再调整排布。缩小用户设置的字体,或把返回入口挤出可触达区域,都只是让布局问题暂时看不见。

图04-A:两侧使用同一菜谱与当前步骤,标出始终保留的摘要、返回入口,以及宽度允许时增加的信息。窗口宽度、字体设置和MiniBar实际状态随图记录。
3. 实现
我通常会先找出这类界面中不应一起变化的状态。在灶台菜谱里,它们至少分为三组:菜谱进度、当前页签,以及MiniBar的呈现状态。
currentStep继续表示RecipeData步骤数组的当前索引,由三个页签共同使用的页面层持有。正文和MiniBar都读取它。MiniBar不保存自己的“当前步骤”,否则我们迟早需要解释两份进度为何不同。
下面是公共页面中的状态与返回处理片段。页签顺序仍为备菜、烹饪、笔记;pendingStep表示应用等待执行的定位请求。
@State currentStep: number = 0;
@State selectedTab: number = 0;
@State isCollapsed: boolean = true;
@State pendingStep: number = -1;
private returnToCurrentStep(): void {
this.pendingStep = this.currentStep;
this.selectedTab = 1;
}
这个方法没有写入currentStep,也没有调用第03篇的handleNext。返回动作即使在烹饪页再次发生,也只要求重新定位当前步骤。
烹饪页接收定位请求后,需要等目标列表具备可定位条件,再滚动到对应步骤并清除请求。这个衔接由页面布局负责。不要用固定延时猜测页面何时准备好,也不要恢复旋转前保存的像素偏移:字体或宽度变化后,同一个偏移可能已经对应另一段文字。
这一划分还限定了状态的生命周期。切页签或重建MiniBar不能重新初始化currentStep;如果宿主页面也被重建,进度的保存与恢复应由它的上层负责,而不是交给迷你栏的构建函数。

图04-E:返回操作连接页签与正文定位,“下一步”连接进度更新,样式回调连接信息排布。三条路径分别标注允许修改的状态。
接着接入HDS。沉浸光感给出了HdsTabs悬浮导航与MiniBar的组合方式。下面截取底栏配置;三个页签的内容沿用已有页面,miniBarBuilder构建摘要和返回入口。
import {
HdsTabs, HdsBarStyle, hdsMaterial
} from '@kit.UIDesignKit';
// HdsTabs的页面构建片段
HdsTabs({ index: this.selectedTab }) {
// 依次构建备菜、烹饪、笔记三个页签
}
.onChange((index: number) => {
this.selectedTab = index;
})
.vertical(false)
.barPosition(BarPosition.End)
.barOverlap(true)
.barFloatingStyle({
systemMaterialEffect: {
materialType: hdsMaterial.MaterialType.ADAPTIVE,
materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
},
miniBar: {
miniBarBuilder: () => this.miniBarBuilder(),
onBarStyleChange: (style: HdsBarStyle) => {
this.isCollapsed = style === HdsBarStyle.COLLAPSE;
}
}
})
barOverlap使页签栏悬浮于内容之上;它同时意味着我们必须检查内容的覆盖关系。onBarStyleChange只更新布局状态。把步骤初始化放进这个回调,会让一次宽度变化意外地成为一次业务操作。
官方指南描述了宽屏默认展开、窄屏默认折叠的适配行为。应用应根据组件报告的状态安排信息,并记录实际窗口宽度,不另外猜一个“手机折叠、平板展开”的设备分类规则。平板也可能运行在较窄的窗口中。
这段配置的材质类型与等级都选择ADAPTIVE。两者属于不同字段:前者选择自适应系统材质,后者让系统决定材质等级。前篇uiMaterial中的THIN是另一套接口中的样式值,不能直接填入这里。普通按钮若改变承载位置,也需要重新核对其材质作用域,不能仅凭它仍在屏幕底部就认定效果相同。
本篇将这些HDS能力与HarmonyOS 7/API 26空间化适配配合使用。hdsMaterial接口接入工程时,应逐项核对MiniBar相关签名与版本约束。
最后处理正文避让。我不建议从一个固定的底部padding开始,因为它把多个不同来源的空间压成了一个数。
先将正文视口和底部遮挡区域转换到同一坐标系。若悬浮组合从overlayTop向下覆盖到视口底部,所需补充空间可以用下面的应用函数表达。所有输入使用同一种长度单位。
function requiredBottomSpace(
viewportTop: number,
viewportBottom: number,
overlayTop: number,
readingGap: number
): number {
const height = Math.max(0, viewportBottom - viewportTop);
const overlap = Math.min(
height, Math.max(0, viewportBottom - overlayTop)
);
return overlap > 0 ? overlap + Math.max(0, readingGap) : 0;
}
这里的overlayTop取实际需要避让的底部组合上缘;readingGap是应用保留的阅读间距。函数只计算额外需要的空间,不决定系统安全区。若布局已经排除了系统区域,就不能再把它的高度加入一次。若存在多个分离的遮挡区域,也应先整理它们的覆盖关系,而不是机械相加各组件高度。
计算出的空间要进入滚动内容的底部布局,使末行能够滚到遮挡区域上方。外层留白和滚动内容的尾部空间并不总有相同效果。barBottomMargin调整悬浮栏位置,也不能替代正文避让。

图04-B:展开与折叠分别标出正文视口、MiniBar、导航栏、系统区域和末行边界。尺寸依据布局测量填写,阅读间距单独标为应用策略。
折叠、展开、旋转和字体变化都会使这些几何关系改变。测量值更新以后,还要检查末行能否完整进入阅读区域。公式帮助我们明确计算对象,末行检查帮助我们确认对象没有选错。
4. 检验
我会先走完查盐的任务,再逐项改变布局条件。只看初始页面,很容易漏掉滚动末端与返回定位的问题。
| 步骤 | 操作 | 应保持的结果 |
|---|---|---|
| 1 | 在烹饪页定位“小火炖煮” | 正文与摘要指向同一currentStep |
| 2 | 切到备菜,查盐的用量 | 数据与RecipeData一致,返回入口可达 |
| 3 | 继续滚动到列表末行 | 末行能完整进入无遮挡区域 |
| 4 | 通过MiniBar返回 | 定位原步骤,完成标记不变 |
| 5 | 分别加入旋转或宽度变化后重走路径 | 布局变化不修改步骤进度 |
把初始索引记为s,这条路径结束时仍应为s。途中没有执行“下一步”,因此任何索引变化都值得检查。补充一条“烹饪→笔记→返回”的路径,可以发现状态是否只在某两个页签间正确共享。

图04-C:烹饪、备菜、MiniBar返回、原步骤四个节点均标出currentStep。旋转和窗口变化作为支路插入,同一任务中的步骤索引应保持不变。

图04-F:按窗口和字体条件组织检查表,将末行可见、摘要完整、入口可达、步骤保持分别记录,预期与实际结果分列。
随后在窄、宽窗口中分别检查默认字体和大字体。每条记录保存窗口宽高及单位、方向、MiniBar样式、初始与最终页签和步骤,以及遮挡、裁切、入口可达性。设备、系统、SDK、主题和材质配置用于解释环境差异;截图与录屏位置用于找回具体表现。
布局改变也可能暴露原来的交互边界。第03篇的六类用例仍然适用:按下拖出、正常点击、快速连续点击、末步骤、禁用态和关闭光效。正常有效点击推进一项,连续有效点击逐次推进并在末步停止;取消、禁用和返回操作均不推进。到达末步仍显示“已到最后一步”,不能把导航重排变成“烹饪完成”。
材质变化后的文字也要重看。浅深主题、背景变化和实色回退下,用户都应能辨认摘要与返回入口。一个只有在最理想背景中才读得清的入口,无法承担持续导航的职责。
对于失败,我会同时保存操作路径和状态记录。摘要被截断与currentStep被重置是不同问题,前者需要调整信息容量,后者需要检查状态归属。修正后重复原条件,再重走完整任务,避免局部修复破坏另一种布局。
这些检查可以支持功能与布局判断。若要比较A、B方案的任务效率,还需要记录参与者、起点、操作次数、错误与用时。没有对应记录,就不从截图推断效率提升;设备覆盖不足的部分也应在验证记录中保留。
5. 结尾
当前步骤入口需要持续表达同一件事:从这里可以回到正在做的操作。折叠与展开改变的是信息容量,菜谱进度和返回含义应保持稳定。
这一设计可以留下三份可复用的东西:信息分配表决定空间不足时保留什么,底部避让核对表决定正文怎样让出实际覆盖区域,任务回归脚本检查用户是否真的回到原步骤。
交付前,我会再次核对:安全区是否重复计入,折叠和展开是否重新测量,末行是否可读,大字体下入口是否可达,以及返回是否保持原进度。这些问题比底栏看起来是否轻盈更接近用户的任务。
MiniBar仍属于同一应用内的布局。手机和平板帮助我们检查不同宽度,不承担跨设备接力。下一篇将在这套布局上加入本地步骤视频,讨论动态内容与材质相遇时的绘制成本;文字、反馈和末行可见性的检查会继续保留。
更多推荐



所有评论(0)