
【HarmonyOS 7新能力044】平行视界工程封装把接入逻辑放进可维护的分层结构列表与详情在手机上适合前后跳转到了平板或 2in1如果仍把详情铺满整块大屏不但浪费空间也破坏用户边看边选的连续性。平行视界类布局能让同一应用的两个关联页面并排协同但真正难点是窗口尺寸变化、返回栈、选中状态和编辑草稿如何保持一致。单纯把两个页面放进Row无法解决导航语义。说明本文的ParallelViewPort、PaneCoordinator等为教学抽象并非 HarmonyOS SDK 的真实接口。平行视界支持范围、窗口能力、配置与 API 签名请以当前官方文档和目标 SDK 为准。1. 分屏首先是导航模式变化单栏模式中列表进入详情会压入新路由双栏模式中列表保留在主区详情在从区替换。两种模式应表达同一用户任务而不是维护两套路由。export interface NavigationIntent { route: string params: Recordstring, string relation?: primary | detail | supporting sourceId?: string }页面只发出导航意图布局协调器根据当前模式决定压栈还是更新从区。2. 四层架构稳定职责页面与导航层负责内容和用户事件分屏编排层决定主从配对、比例和切换窗口能力适配层读取实际尺寸与系统支持路由与状态仓库保存逻辑路径、选中项和恢复快照。export interface ParallelViewPort { inspectWindow(): PromiseWindowSnapshot applyLayout(layout: PaneLayout): PromiseApplyResult } export interface RouteStateStore { getSnapshot(): PromiseRouteSnapshot saveSnapshot(snapshot: RouteSnapshot): Promisevoid }平台能力变化停在适配层业务页面无需判断具体设备型号。3. 用有效宽度判断布局设备类别不能代表当前窗口。平板处于小窗时也可能只能单栏2in1 拖动窗口后则会跨越断点。决策应使用内容区有效宽度、方向和安全区域。export interface WindowSnapshot { widthVp: number heightVp: number orientation: portrait | landscape avoidLeftVp: number avoidRightVp: number epoch: number } function effectiveWidth(window: WindowSnapshot): number { return window.widthVp - window.avoidLeftVp - window.avoidRightVp }断点来自设计与实机验证不在多个页面重复硬编码。4. 页面配对需要显式规则不是任意两个页面都适合并排。列表与详情、目录与阅读、设置分类与配置页通常具有明确主从关系。配对表定义允许组合、最小宽度和空详情行为。export interface PanePairRule { primaryRoute: string detailRoutes: readonly string[] minWidthVp: number emptyDetail: placeholder | autoSelect | hidden }未知页面回到单栏导航不能把不相关内容强行拼在一起。5. 平行视界布局决策流程流程依次采样窗口、判断断点、选择页面配对、同步状态、应用布局并在窗口或路由变化后恢复。小窗、页面缺失与状态冲突走安全旁路。export type PaneMode single | dual export interface PaneDecision { revision: number mode: PaneMode primaryRoute: string detailRoute?: string ratio?: number reason: string }决策与渲染分离纯策略函数可以用多组窗口尺寸测试。6. 路由栈只保留一份逻辑历史如果单双栏各自维护历史窗口变化后返回行为会混乱。仓库保存逻辑访问序列渲染层只选择如何投影到一个或两个容器。export interface LogicalRouteEntry { id: string route: string params: Recordstring, string parentId?: string visitedAt: number } export interface RouteSnapshot { entries: readonly LogicalRouteEntry[] activeId: string selectedEntityId?: string }从双栏缩回单栏时当前详情成为顶层活动路由返回后回到对应列表状态。7. 主从状态通过标识同步列表不要直接引用详情组件实例。主区发布选中实体 ID详情区通过 ViewModel 或仓库读取对应数据编辑结果提交后更新同一数据源。export interface SelectionState { entityId?: string sourceRoute: string revision: number } function selectEntity(current: SelectionState, entityId: string): SelectionState { if (current.entityId entityId) return current return { ...current, entityId, revision: current.revision 1 } }版本号帮助拒绝旧异步请求避免快速选择时详情回跳。8. 输入草稿不能随布局切换丢失窗口跨越断点可能销毁并重建容器。编辑草稿属于业务状态必须由页面 ViewModel 或专用仓库持有不应只放在组件局部临时变量中。export interface EditDraft { entityId: string fields: Recordstring, string dirty: boolean updatedAt: number } export interface DraftStore { save(draft: EditDraft): Promisevoid restore(entityId: string): PromiseEditDraft | undefined }布局变化只重建视图不触发自动提交或清空。退出未保存内容仍按产品规则确认。9. 比例设计要有最小约束双栏不是简单五五分。列表需保证标题和操作可读详情需保留内容最小宽度。比例策略结合有效宽度计算并在约束无法同时满足时退回单栏。export function resolveRatio(width: number, primaryMin: number, detailMin: number): number | undefined { if (width primaryMin detailMin) return undefined const preferred 0.38 return Math.max(primaryMin / width, Math.min(preferred, 1 - detailMin / width)) }分割线、拖动和焦点状态同样需要清晰的视觉与键盘反馈。10. 窗口变化采用版本化恢复旋转、折叠或拖动可能连续产生多个窗口事件。协调器对事件去抖并给决策递增版本旧布局应用结果不得覆盖新决策。async function applyLatest(decision: PaneDecision): Promisevoid { const result await adapter.applyLayout(toLayout(decision)) if (decision.revision ! state.desiredRevision) return if (result.ok) state.confirm(decision) else state.fallbackToSingle(result.code) }失败时保持单栏可用路径不让两个半完成页面同时出现。11. 返回键与焦点必须一致双栏时返回可先关闭临时详情或回到上一个逻辑实体单栏时按逻辑路由栈返回。鼠标、键盘和触控需要一致焦点顺序详情更新后不应无故把焦点抢回主区。export function resolveBack(snapshot: RouteSnapshot, mode: PaneMode): BackAction { if (mode dual hasTransientDetail(snapshot)) return { kind: closeDetail } if (snapshot.entries.length 1) return { kind: popLogicalRoute } return { kind: systemBack } }根页面返回遵循系统行为不制造无法退出的自定义循环。12. 验收清单与总结测试覆盖手机竖屏、横屏、小窗、平板单双栏切换、2in1 连续缩放、深浅主题和大字体。验证列表滚动位置、选中项、详情内容、编辑草稿和返回栈在切换后保持一致页面缺失或布局调用失败时能退回单栏底部操作避开系统导航区域。平行视界的工程本质是同一逻辑导航在不同窗口中的两种投影。通过显式页面配对、单一逻辑路由栈、共享状态、版本化布局决策和安全回退应用才能在大屏充分利用空间又在窗口变化时保持连续、可理解的用户任务。十三、接入前检查清单平行视界工程封装把接入正式接入前工程侧要过一遍检查清单SDK 版本与目标 API 版本匹配、权限在清单中声明且按需申请、能力开关在专用配置层管理、包体积增量符合预算。任何一项不过就先停带着不匹配的版本往上冲排错成本会翻倍。interface PrecheckItem { name: string; pass: boolean; note?: string } const checklist: PrecheckItem[] [ { name: SDK/API版本匹配, pass: false }, { name: 权限声明完整, pass: false }, { name: 开关集中管理, pass: false }, { name: 包体积增量预算, pass: false }, ] const allPass checklist.every(x x.pass)检查清单跑通的标志是新建工程按清单接入一次成功且不依赖任何口头经验。做不到说明文档还缺关键步骤。十四、常见坑与版本适配记录平行视界工程封装把接入最常踩的坑集中在三处示例代码用的旧接口在新版本被拆分、默认配置与生产预期不一致、调试环境与真机行为有差异。每踩一个坑就记进版本适配表注明坑点、影响版本、修复方式与验证方式。interface Pitfall { issue: string; sinceApi: string; fix: string; verifiedOn: string[] } const pitfalls: Pitfall[] [] function knownPit(api: string): Pitfall[] { return pitfalls.filter(p p.sinceApi api) }适配表的价值在升级时兑现每次目标版本变更先对照表过一遍已知坑能省下一整轮盲目排查。十五、回归口径与停止线平行视界工程封装把接入的任何改动合入前都要回答同一个问题怎么证明没有把好的部分改坏。回归口径必须落在纸面上一版通过的用例清单全量重跑、上一版记录的基线值全量对比、新增能力补一条专属用例。三件事缺一件回归就是走过场。interface RegressionGate { lastPassedCases: number; rerun: number; baselineCompare: PASS | FAIL; newCaseAdded: boolean } function gateOk(g: RegressionGate): boolean { return g.rerun g.lastPassedCases g.baselineCompare PASS g.newCaseAdded }停止线同样要提前写明同一问题打回三次、回归连挂两轮、或验证环境与目标环境指纹不一致满足其一就停下来复盘流程而不是继续硬修。把停止线当成流程的一部分才能避免在小问题上耗掉整块的排期。