HarmonyOS 7 TwinShelf 平行视界工程实录 02:NavigationMode × Split Ratio:1:2/2:1分栏比例、右栏连续路由与返回收口【鸿蒙心迹】
01 把 TwinShelf 最基础的平行视界链路做稳了。
宽窗口下:
NavigationMode.Auto
→ Split
左侧文章目录持续存在,右侧显示 article_042;整个应用仍然只维护一份 NavPathStack 和一份 selectedArticleId。连续点击同一详情时,重复路由被拦截,滚动位置 612vp 也没有因为分栏出现而重置。
第二篇开始,我遇到一个更像真实阅读产品的问题:
右栏不是终点。
用户在 article_042 详情里继续点“相关推荐”,会进入 related_107,然后又打开 related_205。这时系统负责把页面放到右侧,但“右栏到底有几层、返回应该退谁、左栏高亮是否变化”仍然属于应用自己的 Navigation 状态。
同时 HarmonyOS 7 当前官方月刊已经明确:平行视界在原有大屏分栏基础上新增 1:2 与 2:1 分栏比例,并新增多种路由配置字段,用于覆盖更多大屏业务场景。
所以 02 把两个问题放在一起处理:
分栏比例可以变化;右栏路由也可以继续变深;但左栏主选择与返回语义必须稳定。
本轮统一数据:
taskId:
parallel_20261003_02
window:
1440 × 900vp
routeMode:
NAVIGATION
ratioSequence:
1:2 → 2:1 → 1:2
ratioChanges:
3
ratioProjectSnapshot:
480:960
→ 960:480
→ 480:960
selectedCategory:
技术实践
selectedArticleId:
article_042
relatedRoute1:
related_107
relatedRoute2:
related_205
rightStackDepth:
1 → 2 → 3 → 2 → 1
backEvents:
2
leftSelectionRetained:
true
detailScrollOffset:
612vp
duplicateRouteBlocked:
2
routeRestoreCount:
1
avgRatioSwitchCost:
3.1ms
finalRatio:
1:2
finalRightRoute:
article_042
status:
ROUTE_RATIO_STABLE

一、先说清楚:比例由平行视界负责,路由仍然由 Navigation 负责
HarmonyOS 7 的平行视界可以提供:
1:1
1:2
2:1
等大屏分栏关系。
但 TwinShelf 不把:
1:2
理解成新的路由模式。
无论比例怎么变,当前业务路由仍然是:
ArticleList
→ ArticleDetail(article_042)
→ ArticleDetail(related_107)
→ ArticleDetail(related_205)
比例只是:
左右区域怎么分配空间。
这两个维度必须拆开。
二、1:2 和 2:1 的项目调试数据只是“结果快照”
当前测试窗口:
1440×900vp
为了让调试数据直观,TwinShelf 把系统最终分栏结果记录成近似项目快照:
1:2
约 480 : 960
2:1
约 960 : 480
这些数用于当前 Demo 验收。
真实系统还可能受到:
窗口边距
系统分割条
最小内容宽度
设备形态
影响。
所以文章里不会写成:
1:2 永远严格等于 480 / 960vp。
应用真正要关心的是:
在 1:2 或 2:1 下,自己的内容层级和路由状态是否仍然正确。
三、右栏连续打开相关内容,先做路由去重
本轮右栏:
article_042
→ related_107
→ related_205
如果用户快速点两次同一相关推荐,还是会出现 01 的重复路由问题。
第一段代码继续复用 RouteCoordinator:
export class RouteCoordinator {
constructor(
private stack:
NavPathStack
) {}
openRelated(
articleId:
string
): boolean {
const routes =
this.stack.getPathStack()
const top =
routes[
routes.length - 1
]
const param =
top?.param as
Record<string, Object>
const topId =
param?.articleId as string
if (
top?.name ===
'ArticleDetail' &&
topId === articleId
) {
return false
}
this.stack.pushPath({
name:
'ArticleDetail',
param: {
articleId,
source:
'related'
}
})
return true
}
}
本轮故意做了两次重复点击:
duplicateRouteBlocked=2
所以右栏深度真实变化:
1
→ 2
→ 3
没有多出来的幽灵页面。
四、左栏选中项不应该随着 related 路由变化
右栏已经显示:
related_205
但左栏仍然高亮:
article_042
因为左栏表示:
当前主阅读入口。
相关推荐只是:
右栏内部继续浏览。
如果每次 push related 都改:
selectedArticleId
左栏会突然滚去找一个原本不在当前列表位置的文章,阅读上下文反而乱了。
本轮:
leftSelectionRetained=true
就是专门验证这件事。
五、NavigationMode 的变化也不应该清空右栏栈
用户在 2:1 比例下已经打开:
related_205
如果系统窗口临时缩窄进入 Stack,再恢复 Split,业务栈还是:
article_042
related_107
related_205
不应该:
一变成单栏
就 resetPathStack
Navigation 的显示模式变化是表现层变化。
路由栈本身才是业务访问历史。
TwinShelf 只在“业务要求重新开始”时重置栈。
六、返回只退右栏,不重置左栏主选择
第二段代码解决 02 最核心的行为:
handleBack(): boolean {
const routes =
this.pathStack
.getPathStack()
if (routes.length > 1) {
this.pathStack.pop()
this.rightStackDepth =
routes.length - 1
this.backEvents++
return true
}
return false
}
第一次 Back:
related_205
→ related_107
第二次 Back:
related_107
→ article_042
最终:
rightStackDepth=1
finalRightRoute=article_042
整个过程中:
selectedArticleId=article_042
一直不变。
七、为什么 rightStackDepth 只是项目概念,不是第二个 NavPathStack
这里最容易误解。
TwinShelf 日志写:
rightStackDepth
只是为了描述:
当前主详情之后
右侧连续浏览了几层内容。
它不代表项目真的创建:
rightNavPathStack
实际只有一个:
NavPathStack
这点必须和 01 保持一致。
否则左右两个独立栈的返回关系会很难收口。
八、比例切换时不要用“重新 push 当前页”刷新右栏
第一版我在 Ratio 改变时做过:
重新 push 当前 route
让右栏重新布局
结果比例每改一次,栈就多一层。
真正正确的做法是:
比例变化
→ 只更新布局状态 / 等待系统布局结果
路由不变
这也是本轮:
ratioChanges=3
routeRestoreCount=1
而不是:
ratioChanges=3
routePushes=3
的原因。
九、第三段代码只记录比例结果,不接管系统平行视界
export type SplitRatio =
'1:2' | '2:1'
export interface RatioSnapshot {
ratio: SplitRatio
leftVp: number
rightVp: number
}
export class SplitRatioProbe {
samples:
RatioSnapshot[] = []
record(
ratio:
SplitRatio,
leftVp:
number,
rightVp:
number
): void {
this.samples.push({
ratio,
leftVp,
rightVp
})
}
}
这段代码只是调试探针。
真正的平行视界 1:2 / 2:1 能力由系统配置与当前平台能力负责。
TwinShelf 只把最终观测值留下来,用于后续回归。
十、为什么 2:1 适合某些文章场景,但不是默认结论
阅读应用里:
左侧目录
右侧正文
一般右侧需要更宽,所以:
1:2
更自然。
但某些场景:
左侧复杂筛选 / 知识图谱
右侧只看摘要
2:1 也可能合理。
HarmonyOS 7 增加比例能力的价值就在这里:
系统提供更多大屏分配方式,业务根据内容层级选最合适的比例。
TwinShelf 当前默认:
1:2
只是项目选择。
十一、比例变化不能改变详情滚动位置
本轮:
detailScrollOffset=612vp
从:
1:2
→ 2:1
→ 1:2
全过程不重置。
否则用户只是把左右空间调了一下,却突然回到文章顶部,会非常明显。
滚动位置继续属于:
ArticleSessionStore
不是 Ratio 状态。
十二、右栏 Back 的判断不能只看“当前是不是 Split”
窄窗 Stack 模式里,同样可能存在:
article_042
→ related_107
→ related_205
所以 Back 行为应该看:
NavPathStack
而不是:
if mode == Split
这一点看起来很小,却决定了平行视界和手机单栏是否真正共享一套导航语义。
TwinShelf 的 RouteCoordinator 不读设备类型,也不读“是不是平板”,只读当前业务栈。
十三、比例变化时列表滚动和分类也必须连续
左栏当前分类:
技术实践
比例从 1:2 切到 2:1 时,左栏变宽,但它不应该:
自动回全部文章;
滚动回顶部;
重新选择第一篇。
当前仍保持:
selectedCategory=技术实践
selectedArticleId=article_042
这样用户把空间调给左栏以后,看到的是同一上下文的更多内容,而不是重新开始浏览。
十四、DevEco 图里把路由栈和比例变化放在同一条日志里
开发图:

HiLog:
taskId=
parallel_20261003_02
window=
1440x900vp
mode=
NAVIGATION
ratio=
1:2
selected=
article_042
push related_107
rightDepth=2
ratio:
1:2 → 2:1
push related_205
rightDepth=3
back
3 → 2
back
2 → 1
finalRoute=
article_042
duplicateBlocked=
2
restoreCount=
1
avgRatioSwitch=
3.1ms
status=
ROUTE_RATIO_STABLE
这条日志比三张静态双栏图更有用。
十五、运行图把“比例”和“路由”分成两块看
最终运行图:

上半部分:
1:2
→ 2:1
→ 1:2
中间:
article_042
→ related_107
→ related_205
→ back
→ back
下方:
leftSelectionRetained=true
duplicateRouteBlocked=2
avgRatioSwitchCost=3.1ms
finalRightRoute=article_042
最终:
ROUTE_RATIO_STABLE
十六、HarmonyOS 7 的 1:2 / 2:1 更适合被理解成“内容策略”
官方 2026 年 6 月开发者月刊明确提到,HarmonyOS 7 平行视界在大屏分栏基础上新增 1:2 和 2:1,并新增多种路由配置字段。
对应用来说,最值得利用的不是“数字多了两个”。
而是可以把:
导航型
阅读型
对比型
筛选型
不同内容结构映射到更合适的分栏关系。
TwinShelf 02 先从阅读场景验证最基础的一组。
十七、02 最后固定八组反向测试
第一组,1:2 初始显示 article_042。
第二组,打开 related_107,右栏深度 2。
第三组,切 2:1,路由栈不变化。
第四组,打开 related_205,右栏深度 3。
第五组,连续点击 related_205,不重复入栈。
第六组,两次 Back 后回到 article_042。
第七组,左栏 selectedArticleId 全程保持 article_042。
第八组,详情滚动位置 612vp 不因比例变化重置。
全部通过以后:
ROUTE_RATIO_STABLE
才成立。
十八、下一篇开始处理“临时离开分栏”
右栏继续浏览以后,真实产品还会遇到:
图片预览要全屏;
视频要横屏;
登录 / 支付页不适合分栏;
过渡页只应该短暂存在。
如果临时全屏前没有保存右栏快照,回来后很容易:
AttachmentPreview
→ ArticleList
把右栏阅读上下文弄丢。
03 会继续同一个 TwinShelf:
Transition Page
Fullscreen
Route Snapshot
Split Restore
把“暂时离开平行视界,再准确回来”做成一条完整工程链。
十九、02 真正稳定的是“空间变化不改导航事实”
到这一篇以后,TwinShelf 已经可以明确区分:
空间层:
1:2 / 2:1
导航层:
article_042 → related_107 → related_205
主选择:
article_042
三者可以同时变化,也可以各自保持。
只要这个分层清楚,后面做临时全屏、横屏和应用内分屏时,就不会因为“画面看起来换了位置”而误操作业务路由栈。
二十、路由快照比“当前页面名”更适合做恢复
临时切换窗口、进入后台、或者后面 03 打开全屏页时,只有:
currentPage=related_205
是不够的。
因为要恢复完整上下文,至少需要知道:
article_042
related_107
related_205
整个访问链。
所以 TwinShelf 定义轻量快照:
export interface RouteSnapshot {
selectedArticleId:
string
routeNames:
string[]
routeParams:
Record<string, Object>[]
detailScrollOffset:
number
}
保存时从:
NavPathStack.getPathStack()
提取业务字段。
恢复时再用:
setPathStack()
一次性恢复合法栈。
这比“恢复时连着 push 三次”更不容易出现中间态和动画抖动。
二十一、返回键真正要防的是“右栏已经到底,还继续误退左栏”
当右栏只剩:
article_042
时,TwinShelf 认为已经到当前主阅读入口。
这时如果大屏仍处于分栏:
再 Back
不应该先把左栏 selectedArticleId 清空。
项目会把下一步交给更高层返回策略:
退出当前阅读上下文;
回到上一业务入口;
或者让系统处理。
也就是说,右栏内部 Back 和应用全局 Back 不是同一个层级。
02 先只处理:
3 → 2 → 1
后续 03 全屏恢复时再继续扩展。
二十二、分栏比例变化不能触发图片和正文重新解码
阅读详情里通常有:
大图
富文本
代码块
媒体资源
如果 1:2 → 2:1 时因为父布局宽度变化就重新拉取、重新解码所有资源,比例切换会明显变卡。
TwinShelf 当前只重新做:
布局测量
文本换行
图片区尺寸
不会:
重新 fetch
重新 decode
重新创建文章 ViewModel
所以本轮 avgRatioSwitchCost=3.1ms 只代表项目侧状态与布局提交,并不把网络与大图加载混进来。
这个口径后面做性能回归时会保留。
二十三、长标题与窄右栏是 2:1 模式下必须额外测的边界
2:1 时右侧会明显变窄。
related_205 当前标题较短,没有问题。
但真实文章可能有:
60 字标题
长作者名
多标签
收藏 / 分享 / 更多按钮
TwinShelf 在 2:1 验收时额外要求:
标题最多两行;
关键按钮仍可见;
正文宽度不小于项目下限;
超长元信息允许折叠。
比例适配不只是“系统能把两栏画出来”。
内容密度也要跟着变化。
二十四、比例切换时 Toolbar 与目录也需要自己的响应策略
当前 1:2:
右栏宽
→ 收藏 / 分享 / 更多 全显示
2:1:
右栏窄
→ 保留收藏
→ 分享 / 更多收进菜单
但这种工具栏变化仍然不修改:
NavPathStack
selectedArticleId
detailScrollOffset
它只是密度策略。
这和前面 FlexDesk 系列的经验类似,但这里的主线已经换成平行视界的“系统分栏 + 路由连续”。
二十五、路由去重最好在 Coordinator 层统一,而不是每个卡片自己判断
如果:
推荐卡片 A
推荐卡片 B
目录点击
搜索结果点击
每个入口都自己写一遍:
if currentId == targetId
迟早会有一个入口忘记。
TwinShelf 把所有详情进入统一收口到:
RouteCoordinator.openArticle / openRelated
页面只发:
我要打开哪个 articleId
至于:
是否重复
应该 push 还是 replace
是否需要恢复 selected
都由 Coordinator 决定。
到 03 临时全屏时,这个中心化路由层会更重要。
二十六、后台恢复时先恢复路由,再恢复滚动
如果应用在:
related_205
612vp
位置进入后台,回来以后先设置 scrollOffset,却还没有把正确 ArticleDetail 恢复出来,滚动恢复可能作用到错误页面。
所以恢复顺序固定:
恢复 selectedArticleId
→ setPathStack
→ 等对应 Detail 可用
→ 恢复 detailScrollOffset
这个顺序已经写入 TwinShelf 的恢复协议。
当前本轮没有触发后台恢复,但 routeRestoreCount=1 已经用“比例 + 窗口形态恢复”验证同一套流程。
二十七、ROUTE_RATIO_STABLE 的完整含义
到 02 结束时,这个状态至少代表:
系统分栏比例变化不会制造新路由;
右栏 related 链能连续压栈;
重复详情会被 Coordinator 拦截;
Back 只按业务栈逐级回退;
左栏主选择 article_042 不漂移;
详情滚动 612vp 不重置;
最终仍能回到 1:2 + article_042;
内容没有因为比例变化重建。
这组基线会直接成为 03 临时全屏前的“正确现场”。
只有先知道正常分栏应该是什么样,下一篇才能判断全屏回来以后到底恢复对没有。
参考资料
- HarmonyOS 7 2026 年 6 月开发者月刊:
https://developer.huawei.com/consumer/cn/monthly/202606 - HarmonyOS 7 平行视界官方示例:
https://developer.huawei.com/consumer/cn/samples/ - Navigation 分栏开发:
https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/arkts-navigation-split-mode - Navigation / NavPathStack API:
https://developer.huawei.com/consumer/cn/doc/doccenter-references/api/ts-basic-components-navigation
更多推荐




所有评论(0)