欢迎光临
我们一直在努力

HarmonyOS 7 TwinShelf 平行视界工程实录 04:Landscape × Virtual Container:横屏请求、虚拟容器与内部页面状态隔离【鸿蒙心迹】

03 解决的是一种非常明确的“临时展示”:

文章
→ 过渡页
→ 全屏 Gallery
→ 返回文章

全屏 14.2 秒以后,article_042 和 612vp 阅读位置都能回到原处。

但真实阅读应用里,还有一类交互完全不同。

例如用户正在读一篇性能文章,右侧想继续:

看图表
记笔记
看讨论
再回图表

这些不是“短暂弹出去看一下”。

它们更像:

同一个文章上下文里的内部工作区。

HarmonyOS 当前官方平行视界示例已经把“路由跳转过程请求横屏显示”和“开启虚拟容器能力”列为典型场景。TwinShelf 04 不去虚构一个系统级 VirtualContainer() ArkUI 组件,而是按照这个场景思路做项目侧实现:

外层 Navigation
继续维护文章级路由;

reader_workspace
组织 Chart / Notes / Discussion 三个内部页面;

横屏只改变 Window Orientation;

内部页面切换不继续污染外层 NavPathStack。

本轮统一数据:

taskId:
parallel_20261003_04

window:
1440 × 900vp

selectedArticleId:
article_042

outerRoute:
ArticleDetail

outerStackDepth:
2

virtualContainerId:
reader_workspace

virtualContainerEnabled:
true

containerPages:
ChartView
NotesView
DiscussionView

containerPageCount:
3

internalSequence:
Chart
→ Notes
→ Discussion
→ Notes
→ Chart

internalSwitches:
4

avgInternalSwitchCost:
2.7ms

orientation:
LANDSCAPE

orientationRequests:
2

orientationSuccess:
2

orientationRestores:
2

notesDraftChars:
86

discussionScrollOffset:
344vp

detailScrollOffset:
612vp

chartStateRetained:
true

leftSelectionRetained:
true

contentRebuildCount:
0

status:
VIRTUAL_CONTAINER_READY

一、虚拟容器最重要的价值不是“有三个 Tab”,而是隔离路由层级

如果没有内部工作区,最直接的写法是:

ArticleDetail
→ push Chart
→ push Notes
→ push Discussion
→ push Notes
→ push Chart

很快外层 NavPathStack 就会从:

depth=2

涨到:

depth=7

用户按返回键时,会连续退五次内部工作页。

这明显不符合阅读语义。

TwinShelf 的原则是:

文章级导航
属于外层 Navigation;

文章内部工作区
属于 reader_workspace。

所以本轮不管内部怎么切:

outerStackDepth
始终=2。

二、进入 Workspace 前先请求横屏,但横屏不是业务路由

当前官方 Window 能力支持:

setPreferredOrientation(…)

TwinShelf 在进入 Workspace 时:

async enterWorkspace(): Promise<void> {
const win =
await window.getLastWindow(
this.uiContext.getHostContext()
)

await win.setPreferredOrientation(
window.Orientation.LANDSCAPE
)

this.orientationRequests++
this.orientationSuccess++

this.store.open(
'reader_workspace',
this.articleStore
.selectedArticleId
)
}

这里横屏只改变:

Window Orientation

不会:

push 一个 LandscapeRoute。

否则方向变化和业务导航又会重新耦合。

三、为什么 Workspace 继续绑 article_042

reader_workspace 并不是一个全局工具页。

它有明确 Owner:

article_042

里面的 Chart、Notes、Discussion 都围绕当前文章。

所以进入容器时保存:

articleId
detailScrollOffset

内部页面不需要重新问:

“你现在看哪篇文章?”

这也是左栏选中态为什么始终保持。

四、内部页面切换用 Store,不继续 Push 外层 Navigation

第一段代码解决的是:三张内部页面怎么切都不改变外层路由深度。

export enum WorkspacePage {
CHART,
NOTES,
DISCUSSION
}

export class ReaderWorkspaceStore {
articleId: string =
'article_042'

current:
WorkspacePage =
WorkspacePage.CHART

notesDraft:
string = ''

discussionScrollVp:
number = 344

switchTo(
page: WorkspacePage
): void {
this.current = page
}
}

本轮内部序列:

Chart
→ Notes
→ Discussion
→ Notes
→ Chart

internalSwitches=4。

外层仍然是:

ArticleDetail
depth=2

五、虚拟容器内部状态必须独立保存

Notes 写了:

86 字

Discussion 滚到:

344vp

Chart 当前筛选和图表状态也保持。

如果切 Tab 以后组件重建:

Notes 草稿归零;
Discussion 回顶部;
Chart 筛选丢失。

这就失去了“工作区”的意义。

所以内部页面的状态 Owner 是:

ReaderWorkspaceStore

不是某个 TabContent 局部变量。

六、第二段代码把三页作为一个工作区呈现

@Component
struct ReaderWorkspacePage {
private store:
ReaderWorkspaceStore =
ReaderWorkspaceStore
.getInstance()

build() {
Column() {
Tabs({
index:
this.store.current
}) {
TabContent() {
ChartView({
articleId:
this.store.articleId
})
}
.tabBar('图表')

TabContent() {
NotesView({
draft:
this.store.notesDraft
})
}
.tabBar('笔记')

TabContent() {
DiscussionView({
scrollOffset:
this.store
.discussionScrollVp
})
}
.tabBar('讨论')
}
.onChange((index) => {
this.store.switchTo(
index as WorkspacePage
)
})
}
}
}

这里没有:

pathStack.pushPathByName

内部切换只是:

Workspace State

变化。

这就是“路由状态隔离”的核心。

七、官方“虚拟容器”场景和 TwinShelf 项目实现要区分

HarmonyOS 7 官方平行视界示例明确覆盖:

开启虚拟容器能力

但 TwinShelf 文章里的:

reader_workspace

是项目侧工作区 ID 和状态组织方式。

它不等于一个叫:

VirtualContainer()

的 ArkUI 公共组件。

文章只借用官方场景能力的工程思路:

多个内部页面可以组织成一个连续业务容器,不让它们都污染外层分栏路由。

这个边界必须说清楚。

八、横屏请求和容器切换可能同时发生,不能各自重建页面

进入 Workspace 时:

orientation
从当前方向变 LANDSCAPE;

container
从 ArticleDetail 进入 reader_workspace。

两个变化如果分别触发:

页面销毁 / 重建

状态会闪断两次。

TwinShelf 把:

Orientation
Workspace State

都写入稳定 Store。

UI 只根据最新状态重新布局。

当前:

contentRebuildCount=0

九、退出 Workspace 必须先保存内部状态,再恢复方向

第三段代码解决退出顺序:

async exitWorkspace(): Promise<void> {
this.store.persistCurrentState()

const win =
await window.getLastWindow(
this.uiContext.getHostContext()
)

await win.setPreferredOrientation(
window.Orientation
.AUTO_ROTATION
)

this.orientationRestores++

this.store.close()
}

当前:

orientationRequests=2
orientationSuccess=2
orientationRestores=2

方向请求与恢复完全成对。

十、为什么恢复 AUTO_ROTATION,而不是写死 PORTRAIT

TwinShelf 是大屏阅读应用。

退出 Workspace 以后用户可能仍然处在:

平板横屏;
折叠屏展开;
PC 窗口。

如果直接:

PORTRAIT

反而会破坏外层设备姿态。

所以项目恢复:

进入前的 Orientation Snapshot

示例代码里用 AUTO_ROTATION 表达“交还给外层策略”,真正产品会恢复进入 Workspace 前保存的值。

十一、Chart 状态保持比“图表还能显示”更重要

本轮最终回到 ChartView。

要求:

chartStateRetained=true

也就是:

筛选条件
时间范围
高亮数据点

仍然是进入 Notes / Discussion 前的状态。

内部页面切换不能把 Chart 当作“每次重新初始化的数据页”。

十二、Notes 86 字不能因为切去讨论而自动保存或丢失

当前:

notesDraftChars=86

它仍然是草稿。

从 Notes 切到 Discussion:

不自动保存;
不清空 dirty state。

再切回来:

仍然 86 字。

内部页面切换只是展示切换,不应该擅自改变业务事务状态。

十三、Discussion 的 344vp 也属于工作区状态

DiscussionView:

scroll=344vp

切到 Notes,再切回来以后仍然 344vp。

如果每个内部页面都依赖组件是否“恰好还在树上”来保存状态,后续容器实现变化时很容易丢。

所以所有关键状态都明确进入 Store。

十四、DevEco 图重点看 outerDepth 一直是 2

开发图:

HiLog:

taskId=
parallel_20261003_04

enter workspace

article=
article_042

outerDepth=
2

orientation=
LANDSCAPE

virtualContainer=
reader_workspace

enabled=
true

pages=
3

switch:
Chart
→ Notes
→ Discussion
→ Notes
→ Chart

count=
4

avgCost=
2.7ms

notesDraft=
86

discussionScroll=
344vp

chartRetained=
true

leftSelectionRetained=
true

orientationRequests=
2

success=
2

restores=
2

outerDepth=
2

contentRebuild=
0

status=
VIRTUAL_CONTAINER_READY

整个过程中外层 Navigation 没被内部工作区污染。

十五、手机运行图展示的是“虚拟容器状态矩阵”

最终运行图:

上半部分:

reader_workspace
3 pages
Chart / Notes / Discussion

下半部分:

article_042
1440×900vp
outerDepth=2
2/2 orientation
internalSwitches=4
avg=2.7ms
contentRebuild=0

连续性检查:

article retained
612vp retained
internal route isolated
chart state retained
orientation restored

最终:

VIRTUAL_CONTAINER_READY

十六、04 最后固定八组场景测试

第一组,article_042 进入 reader_workspace。

第二组,请求 LANDSCAPE 成功。

第三组,Chart → Notes,外层 depth 不变。

第四组,Notes 草稿 86 字保持。

第五组,Discussion 344vp 保持。

第六组,Chart 状态切出去再回来仍保持。

第七组,退出容器后恢复外层方向策略。

第八组,外层 article_042 / 612vp / depth=2 全部不变。

全部通过后:

VIRTUAL_CONTAINER_READY

才成立。

十七、下一篇:容器内部稳定以后,真正做“应用内分屏”

TwinShelf 到 04 已经解决:

主从分栏
分栏比例
右栏连续路由
过渡页
沉浸全屏
横屏请求
虚拟容器

接下来最接近平行视界“多任务”的场景是:

同一个应用里
再触发一次应用内分屏。

例如左侧继续阅读,右侧临时打开“我的收藏 / 比较阅读 / 引用摘录”工作区。

05 会继续:

In-App Split × State Restore

最后 06 再把平行视界的路由、比例、全屏、横屏、容器和应用内分屏做一轮完整回归。

十八、虚拟容器内部应该有自己的返回语义

reader_workspace 里当前有:

Chart
Notes
Discussion

用户在 Discussion 里按返回,不应该立刻退出整个 Workspace。

TwinShelf 的内部返回优先级是:

如果内部页面有历史
→ 回内部上一页;

如果已经回到默认 Chart
→ 才退出 reader_workspace;

这样外层 Navigation 只看到“进入一次 Workspace、退出一次 Workspace”。

不会因为内部切换五次,就让系统返回要按五次才能回文章。

十九、内部页面状态和外层文章状态需要两套 Snapshot

TwinShelf 现在有两层状态:

OuterArticleSnapshot
article_042
detailScroll=612vp

ReaderWorkspaceSnapshot
currentPage
notesDraft=86
discussionScroll=344vp
chartFilter

这两层不能合成一个超级 Store。

原因是生命周期不同。

用户退出 Workspace:

内部状态可以决定保留多久;
外层文章状态必须立即恢复。

以后如果产品决定“下次进入工作区从 Chart 开始”,只需要清 ReaderWorkspaceSnapshot,不会影响 article_042 的阅读位置。

二十、横屏只请求工作区阶段,不应该污染整个应用

进入 Workspace:

request LANDSCAPE

退出以后:

restore previous orientation

而不是把应用全局方向永久改成横屏。

这对 TwinShelf 很重要,因为普通文章阅读仍然可能在竖屏手机上运行。

“某个路由更适合横屏”和“整个应用只支持横屏”是两个完全不同的产品决定。

04 只实现前者。

二十一、横屏请求失败时,Workspace 仍然要能工作

设备策略、窗口模式或系统状态都可能导致:

setPreferredOrientation
失败。

TwinShelf 不会因为横屏请求失败就让工作区彻底不可用。

回退逻辑:

记录 orientation error
→ 保持当前方向
→ 继续打开 reader_workspace
→ 使用响应式布局降级

只是:

orientationSuccess

不会 +1。

本轮 2/2 全成功,所以没有触发降级。

真正产品应当把“横屏增强体验”和“核心功能可用”分开。

二十二、虚拟容器内部页面也要做去重

用户当前已经在 Notes,再次点击 Notes:

不需要重建 NotesView
不需要重新恢复草稿
不需要 internalSwitches++

项目在 switchTo() 里先比较:

target == current

相同就直接返回。

内部页面越多,这种幂等越重要。

否则状态统计会把“重复点击”误算成真实路由切换,页面也会出现不必要刷新。

二十三、图表、笔记、讨论的加载策略应该不同

Chart 可能需要:

读取统计数据
计算图形

Notes 主要是:

本地编辑状态

Discussion 可能:

网络分页

虚拟容器并不意味着三张内部页必须统一生命周期。

TwinShelf 当前策略:

Chart
首次进入加载,后续保留结果;

Notes
一直保留编辑 Store;

Discussion
保留滚动和第一页缓存,长列表允许分页回收。

“内部路由隔离”解决的是外层栈污染,不等于把所有页面强制永久驻留内存。

二十四、容器关闭时要清理临时资源,但不要清业务草稿

退出 Workspace 时可以释放:

图表渲染对象
临时网络请求
订阅监听

但:

notesDraft=86

如果尚未保存,不能因为容器退出就直接丢。

TwinShelf 的 Store 把:

transient runtime resource

和:

business state

分开。

后面 06 做回归时,会分别检查资源是否释放、草稿状态是否按产品规则保留。

二十五、容器内部切换成本 2.7ms 只代表状态提交

本轮:

avgInternalSwitchCost=2.7ms

它不代表:

Discussion 网络加载也只要 2.7ms。

这里统计的是:

currentPage 改变
内部布局提交
已有状态恢复

数据请求和图表计算另有耗时。

性能指标只有口径明确才有比较价值。

二十六、Virtual Container 不是为了“页面更多”,而是为了让外层路由更简单

做完 04 后,外层仍然只有:

文章列表
ArticleDetail
reader_workspace

而不是:

ArticleDetail
Chart
Notes
Discussion
Notes
Chart
…

这就是虚拟容器思路真正的工程收益。

外层路由更容易:

深链恢复
返回收口
跨形态恢复
状态持久化

内部工作区自己处理局部复杂度。

二十七、VIRTUAL_CONTAINER_READY 的完整边界

最终状态表示:

article_042 外层上下文保持;

外层 NavPathStack depth=2;

reader_workspace 内有 3 个内部页面;

4 次内部切换不污染外层栈;

Notes 草稿 86 字保持;

Discussion scroll=344vp 保持;

Chart 状态保持;

横屏请求与恢复成对;

退出 Workspace 后 article scroll=612vp;

contentRebuildCount=0。

做到这一步以后,TwinShelf 才真正拥有了“同一右栏里的连续工作区”,而不只是三个能互相切换的 Tab。

二十八、内部工作区还要考虑深链恢复

如果应用因为系统回收重新启动,用户之前停在:

article_042
reader_workspace
Notes
draft=86

恢复时不能只把外层 Navigation 恢复到 ArticleDetail,然后丢掉内部工作区。

TwinShelf 会把可恢复状态拆成两层持久化键:

outerRouteSnapshot
workspaceSnapshot

先恢复 article_042 与 612vp,再根据 workspaceSnapshot 决定是否重新打开 reader_workspace 以及恢复到 Notes / Discussion / Chart。这样外层路由恢复和内部容器恢复互相独立,也方便在版本升级后对内部状态做兼容迁移。

二十九、横屏工作区还要验证返回时的焦点和手势上下文

横屏进入 Chart/Notes/Discussion 后,焦点、滚动手势和编辑输入法都可能发生变化。退出 Workspace 时除了恢复 Orientation,还要把交互上下文交还给 ArticleDetail,例如恢复正文可滚动、关闭 Notes 的输入法焦点、解除图表手势监听。否则页面视觉上回到了文章,手势却仍被已经隐藏的 ChartView 消费,会出现“文章滑不动”的隐蔽问题。06 的最终回归会把这类资源与交互监听一并纳入收口检查。

最终,04 的重点不是把页面变成三个 Tab,而是证明同一篇文章可以在横屏和内部工作区之间持续工作,外层 Navigation 仍保持简洁,内部状态也有清晰归属。

这套状态隔离也会直接成为下一篇应用内分屏的基础。

后续继续稳定复用。

参考资料

  • HarmonyOS 平行视界示例:
    https://developer.huawei.com/consumer/cn/samples/
  • 窗口旋转与横屏能力:
    https://developer.huawei.com/consumer/en/doc/harmonyos-guides/rotation
  • Navigation 分栏开发:
    https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/arkts-navigation-split-mode
  • Navigation:
    https://developer.huawei.com/consumer/cn/doc/doccenter-references/api/ts-basic-components-navigation
赞(0)
未经允许不得转载:171主机测评 » HarmonyOS 7 TwinShelf 平行视界工程实录 04:Landscape × Virtual Container:横屏请求、虚拟容器与内部页面状态隔离【鸿蒙心迹】
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址