〖共创稿事节〗HarmonyOS 7 新特性实战(61):上架自检「导航栏 ×9」归零记——从判定措辞反推一个宽屏延伸分支
# 〖共创稿事节〗HarmonyOS 7 新特性实战(61):上架自检「导航栏 ×9」归零记——从判定措辞反推一个宽屏延伸分支
上一篇写了宽屏首页一行四卡,那是一次主动的改造;这一篇是一次被动的返修。2.0.0(2) 提审 AGC,上架自检报告里躺着一组刺眼的不通过项:Mate 80 折叠屏展开态,导航栏未做沉浸式延伸 ×9。更麻烦的是,审核通过之后自检报告被收起,逐页明细和截图都拿不回来——手里的全部线索,只剩判定措辞本身,和自己的代码。
这篇记录整个定位、修复与复跑验证的过程。结论先说:2.0.1(3) 包复跑同一条流水线,6 台设备 142 个用例,导航栏类问题 0 条,修复验证成立。
一、判定措辞本身就是线索
先把判定原文逐字拆开:
> 导航栏未做沉浸式延伸,底部栏与系统手势区/内容区交界处留白或遮挡不一致。
这句话里有三个可用的信息点:
1. 「未做沉浸式延伸」——问题不在导航栏控件本身,而在内容与手势区的交界处理;2. 「留白」——交界处有一段视觉上近似空白的区域;3. 「遮挡不一致」——"不一致"三个字最关键:检测在同一应用里看到了两种不同的行为,才会用这个词。
再结合机型:Mate 80 折叠屏展开态。展开态屏幕宽度大,应用走的是侧导航布局(项目里 840vp 阈值判定的宽屏形态,断点体系按官方「一多」适配指南的建议收敛在窗口宽度上)。而上一轮自检里,窄屏直板机全部通过——窄屏的沉浸式延伸是做过的。窄屏通过、宽屏不通过,应用内两种形态行为不一致,与措辞里的「不一致」对上了。
二、机制定位:一个带宽屏排除的开关
应用根部有一个统一的沉浸框架组件,ShanhaiImmersiveFrame,它做两件事:窗口布局全屏 + 系统栏透明,然后按系统返回的 avoidArea 给内容列让位。内容列的 padding 是这样的:
// common/ui/ShanhaiImmersiveFrame.ets —— 内容列按 insets 让位
Column() {
this.content();
}
.width('100%')
.height('100%')
.padding({
left: this.insets.left,
top: this.insets.top,
right: this.insets.right,
bottom: this.extendContentToBottom ? 0 : this.insets.bottom
})
extendContentToBottom 为 true 时,内容列底部 padding 归零,滚动内容可以延伸到手势区之下——这是沉浸式规范推荐的做法(官方沉浸式设置指南的核心就是布局全屏后由应用自己管理安全区)。它为 false 时,内容停在手势区上沿。
而这个开关在调用点的原值是:
// pages/Index.ets —— 修复前的调用点
ShanhaiImmersiveFrame({
isDarkMode: this.isDarkMode,
surfaceColor: this.shellBg(),
extendContentToBottom: !this.isWideScreen() && deviceInfo.sdkApiVersion >= 23,
isWideScreen: this.isWideScreen()
})
两种形态的行为分叉就在这里:
| 形态 | 导航形态 | extendContentToBottom | 内容列 bottom | 交界处观感 | 自检判定 |
|---|---|---|---|---|---|
| 窄屏(折叠态/直板机) | 浮动底部栏 | true | 0,内容延伸到手势区之下 | 内容贴底,无空白 | 通过 |
| 宽屏(Mate 80 展开态) | 侧导航 | false | insets.bottom,内容停在手势区上沿 | 底条只剩背图层 | 不通过 ×9 |
宽屏不通过的原因清楚了:内容止于手势区上沿之后,底条区域只剩 ShanhaiPageBackdropLayer 背景层——首页底部渐变是浅色纸纹,视觉上就是一条近似空白的浅色带,正是措辞里的「留白」。窄屏走 true 分支延伸到底,宽屏走 false 分支停在上沿,同一个应用两种行为,正是「遮挡不一致」。
追这个排除分支的来历:extendContentToBottom 这个开关是 2026-09-26 一次「让滚动内容延伸至手势区」的提交引入的,从引入那天起就带宽屏排除。查了当时的上下文,更像是写窄屏延伸时的顺手防御(宽屏侧导航当时还没有明确的底部场景),而不是后来有意保留的平板适配——也就是说,这是一个从未被单独验证过的历史分支。
三、为什么敢直接统一:功能净空不靠这个开关
修复的第一直觉顾虑是:宽屏延伸到底,末项内容会不会被手势条永久压住?
答案是页面自身的功能净空与这个开关无关。应用有一层独立的响应式净空计算 ShanhaiResponsive.pageBottomInset(),其中侧导航档给到 36vp,大于手势条约 24vp 的高度——列表末项的滚动终点本来就预留了超出手势条的空间。extendContentToBottom 只决定"视觉层能不能铺到底",页面内容"在哪里停下来"由 pageBottomInset 决定。两层职责分离,所以开关可以放心统一。
于是修复就是一行:
// pages/Index.ets —— 2026-11-07(2.0.1 修复):宽屏分支原来不给 extendContentToBottom,
// 内容列停在手势区上沿,底条只剩背图层浅色纸纹,被 UX 检测判「未做沉浸式延伸、
// 交界处留白」;窄屏内容延伸到手势区之下所以通过 ⇒ 同一应用两种形态表现不一致。
// 页面自身的功能净空由 pageBottomInset() 负责,与本开关无关。
extendContentToBottom: deviceInfo.sdkApiVersion >= 23,
ShanhaiImmersiveFrame 组件本身零改动——@Prop 默认 false 只是组件默认值,唯一调用点已显式传参。

四、验证:三层证据,一层比一层硬
4.1 窄屏回归(Pura X 展开态主屏,浮动栏路径)
窄屏走的是 true 分支,修复理论上对它零影响,但回归还是要跑:Pura X 展开态主屏 1320×2120(vp≈406,浮动栏路径)覆盖安装修复后包,首页、图谱、探索、策展、我的五页前后对比截图逐一核对,行为不变。留证在 acceptance/2026-11-07-immersive-foldable/。

4.2 宽屏不回归(MatePad Pro 13 模拟器,侧导航路径)
宽屏 + 真实手势条的组合(Mate 80 展开态)本地无法复现,能做的是平板模拟器上的不回归证明:MatePad Pro 13(2880×1920,侧导航路径)装修复后包,侧导航左栏正常、列表贴到屏幕底缘、无白条、无布局破坏。
这里要诚实记录一个口径限制:平板模拟器没有导航指示条,insets.bottom 很可能为 0,新旧行为在这台设备上本就无差异。所以这组截图只能证明宽屏路径不被修复破坏,不能证明修复生效——修复是否生效,只能靠下一层证据。

4.3 真机自检复跑(终局验证)
2.0.1(3) 包于 2026-11-08 03:01 上传 AGC,自动触发上架自检,跑了 4 小时 34 分,报告 ID 1326023597394532970。设备池 6 台,全部 HarmonyOS 6.1.1(API 20):
| 机型 | 分辨率 | 形态 |
|---|---|---|
| Mate 80 Pro ×2 | 2832×1280 | 折叠屏 |
| Mate X7 典藏版 | 2444×1080 | 折叠屏 |
| Mate X5 | 1080×2504 | 折叠屏 |
| Mate 60 Pro | 1260×2720 | 直板 |
| Pura 80 | 2760×1256 | 直板 |
结果:导航栏类问题 0 条。上一轮 ×9 的 Mate 80 系,这一轮 2 台全量跑完 142 个用例,一条没有——修复验证成立。同时把上一轮的一个疑点也关掉了:当时曾担心「Mate 80 展开态 vp 不足 840、走窄屏分支,×9 另有机制」,若真是那样,本轮窄屏路径不变、问题应该复现;没有复现,这个证伪假设可以排除。
顺带说明为什么这一轮有 142 个用例、上一轮只有 117:本轮设备池多了平板系折叠机型,用例数随设备形态扩展——这也补上了上一轮"缺平板与 2in1 机型、覆盖不全"的欠账。

五、遗留:两个没有归零的 ×1
如实记录,这一轮还有 2 个 UX 不通过项:
- 状态栏 ×1(Mate X5):上一轮 Mate X6 也判过状态栏 ×1,两轮各 1 次、不同机型,呈低频/特定场景特征。机制未定位——报告收起后无逐页明细,只能猜是窗口尺寸变化(折叠切换/旋转)瞬间系统栏着色没跟上。现有防御是窗口尺寸变化时重申请系统栏着色:
// ShanhaiImmersiveFrame.ets —— 尺寸变了 = 旋转/折叠切换 ⇒ 窗口属性被系统重置过,重新申请
.onAreaChange((_oldValue: Area, value: Area) => {
// ...
if (width !== this.lastWidth || height !== this.lastHeight) {
this.lastWidth = width;
this.lastHeight = height;
this.applyWindowFlags(); // setWindowLayoutFullScreen + 系统栏透明,成对申请
}
})
这套防御用尺寸比较挡住了「申请属性又触发 areaChange」的自激,折叠切换后系统栏着色能恢复——两轮各 1 次的低频 ×1 是否就是它某次没接住,没有现场证据,不下结论。等下一轮自检看是否复现。
- 挖孔区 ×1(Mate 60 Pro,新增):摄像头挖孔区遮挡适配提示。142 个用例里出现 1 次,具体页面未知,同样待观察。
另有转场动效、滑动动效类警告若干,属建议级,不阻塞。平台口径上,测试结论"不通过"仅由这两个 ×1 导致;兼容性、稳定性、功耗、性能全部通过。
这两个 ×1 的共同处境是:判定界面不提供逐条截图明细,只给问题类型、说明、次数、影响机型。低频 ×1 在没有现场证据的情况下强行定位,容易改出新的不一致——上一次 ×9 的教训反着用:先保证已知机制是对的、防御是全的,留证据链,等它复现或消失。
收个尾
复盘这次 ×9,最有价值的不是那一行修复,而是一个方法论:判定措辞是检测方写给开发者看的,它本身就包含定位信息。「留白」指向有一段区域只剩背景层,「遮挡不一致」指向应用内行为分叉——把措辞拆开,对照代码里唯一按形态分叉的开关,答案几乎是自己浮出来的。
反过来,防住这类问题的纪律也只有一条:凡是按形态分叉的开关,两侧都要有证据。!isWideScreen() && ... 这个分支活了二十多天没人动它,因为窄屏(主开发设备)永远走 true 那一侧,false 那一侧从未被看过一眼。如果引入时就留一条「宽屏侧导航 + 真实手势条」的验证记录,这个分支要么一开始就不会存在,要么有明确的保留理由。
修复本身一行,复跑 4 小时 34 分,归零。
附:本篇证据清单
- 取证 README:
acceptance/2026-11-07-immersive-foldable/(机制定位、窄屏五页前后对比、宽屏不回归证明与口径限制、复跑结果全记录)。 - 复跑报告:AGC 上架自检 1326023597394532970(2026-11-08,6 台设备 / 142 用例 / 4h34m45s),上一轮 1324198062409185834(5 台 / 117 用例)。
- 源码:
pages/Index.ets(调用点修复与决策注释)、common/ui/ShanhaiImmersiveFrame.ets(insets 让位与 applyWindowFlags 防御)、common/layout/ShanhaiResponsive.ets(pageBottomInset 侧导航档 36vp)。 - 关联稿件:宽屏形态判定链(840vp 阈值)见系列第 59、60 篇;沉浸框架提根改造见系列第 55 篇。
- 已知遗留:状态栏 ×1(两轮各 1 次,机制未定位)、挖孔区 ×1(本轮新增,待观察);判定界面无逐条明细,两项目前均以防御 + 观察应对。
更多推荐



所有评论(0)