前言
平行视界的官方定义只有一句话:未适配分栏的应用,靠一份配置在宽屏上分成两栏。
但我要的不是"能分",是分完之后每一栏到底有多少像素。因为行情页的指标栅格、K 线图的最小可辨宽度、列表行高,全都是按 vp 写死的;分栏比例一变,右栏就从"够用"掉到"挤变形"。文档里 ratio 只写了"左右页面比例",没给实测宽度,也没说越界会怎样。
所以我把它量了一遍。下面所有数字都是同一台 MatePad Pro(HarmonyOS 7 / API 26)上抓的,误差来源只有取整。
基线:这台机器的坐标系
| 量 | 值 | 怎么来的 |
|---|---|---|
| 屏幕像素 | 2800 × 1840 | ui layout 根节点 [0,0,2800,1840] |
| 密度 | 2.125 | 像素 / vp 反推 |
| 屏幕 vp | 1318 × 866 | 2800/2.125、1840/2.125 |
| 宽/高 | 1.522 | > 1.2,命中 wideWindowMode 的"长方形宽屏"判定 |
wideWindowMode 的触发条件是"窗口宽度 ≥ 600vp 且 宽/高 > 1.2"。这台机器横向 1318vp、比值 1.522,两个条件都满足且余量很大。反过来说,竖屏时宽/高 = 0.657,永远进不了这个分支——这就是平行视界只在横屏分栏的数学原因,不是玄学。
配置字段的取值域
easy_go.json 里我关心的四个字段,取值域直接抄 SDK 自带的 JSON Schema(hms/toolchains/modulecheck/easy_go.json),比文档更精确:
| 字段 | 取值域 | 默认 | 备注 |
|---|---|---|---|
ratio |
字符串,正则 ^[1-9]\d* | [1-9]\d*$ |
1:1 | 文档规定范围 1:2 ~ 2:1,越界取边界值 |
isDraggable |
boolean | false | Schema 里 if isDraggable===true then not required ratio——两者互斥,同时写会构建失败 |
mode |
0 或 1 | 1 | 0=购物模式(右推左),1=导航模式(左固定) |
splitDividerColor |
{light, dark} 8 位十六进制 |
— | 6 位过不了校验 |
还有一条容易忽略的:deviceConfig 层 displayModeOptions 与 settingDisplayOptions 互斥,同一个设备类型节点里只能出现一个。
这个 demo 用的是导航模式:
{
"common": {
"displayModeOptions": {
"wideWindowMode": "routerSplit",
"routerSplitOptions": {
"homePage": "pages/Index",
"relatedPage": "pages/StockDetail",
"mode": 1,
"wideSplit": { "ratio": "1 | 2" },
"splitDividerColor": { "light": "#FF3B6BFF", "dark": "#FF7A9BFF" },
"supportLandscapeFullscreen": false,
"enableInSplitScreen": true
}
}
}
}routerSplit 表示路由由 Router 模块实现;如果页面用的是 Navigation 组件,这里要换成 navigationSplit,两套 options 的字段名一样但 Schema 定义是分开的。
实验一:ratio 越界是静默钳位
把 "ratio": "1 | 2" 改成 "1 | 3"(超出 1:2 下限),重新构建、装机:
Row [0,182,933,317] ← 左栏列表行,右边界 933和 1:2 一模一样。2800 / 3 = 933.33,取整 933。
**没有任何 error、没有 warning、hvigor 全绿。**越界值被静默替换成最近的合法边界。这类"配置写了但没生效、也不告诉你"的行为,只能靠量右边界发现——所以我现在写完配置一定会 dump 一次 ui layout。
实验二:isDraggable 与 ratio 是互斥的,不是"后者失效"
文档原文是"开启后 ratio 字段失效"。实测不是失效,是构建期直接拒绝:
> hvigor ERROR: 00303038 Configuration Error
Error Message: Schema validate failed, at file: ...\easy_go.json
params: { failingKeyword: 'then' }failingKeyword: 'then' 正好对应上面那条条件 Schema。要拖拽,就得把 ratio 整个删掉。
删掉 ratio、只留 "isDraggable": true 之后,默认落点变成 1:1:
Row [0,182,1400,317] ← 2800 / 2 = 1400然后拖分割线,三档吸附全部实测到位:
| 手势 | 左栏右边界 | 比例 |
|---|---|---|
| 默认 | 1400 | 1:1 |
| 向右拖到 2200 | 1866 | 2:1(2800×2/3=1866.7) |
| 向左拖到 300 | 933 | 1:2 |
| 再拖回中间 | 1400 | 1:1 |
吸附点只有这三档,中间位置停不住。也就是说"用户自定义比例"这件事不存在,能做的是"三选"。
实验三:右栏页面到底知道自己多宽
这是我认为最有用的一个探针。在右栏页面根节点上挂 onAreaChange,同时读 UIContext.isEasySplit():
.onAreaChange((_o: Area, n: Area) => {
this.diag = `isEasySplit=${this.getUIContext().isEasySplit()}`
+ ` 本页宽度=${Math.round(n.width as number)}vp`;
})四种状态各测一次,屏幕宽度恒为 1318vp:
| 配置 / 页面 | 左栏 px | 本页宽度 | isEasySplit |
|---|---|---|---|
ratio: "1 | 2" 右栏详情 |
933 | 878vp | true |
isDraggable 拖到 1:1 |
1400 | 658vp | true |
isDraggable 拖到 2:1 |
1866 | 439vp | true |
fullScreenPages 投资笔记页 |
— | 1318vp | false |
几个能直接拿去用的结论:
- 878 / 1318 = 0.666,658 / 1318 = 0.499,439 / 1318 = 0.333。页面拿到的宽度和视觉分栏比例严格一致,没有隐藏 padding,可以放心按 vp 写响应式。
- 全屏页
isEasySplit返回 false,且宽度等于整屏。这个布尔值是目前唯一可靠的运行时判据——文档的常见问题里也承认"运行时无法仅通过配置预知运行平行视界分栏模式"。 - 2:1 时右栏只剩 439vp(934px)。同一套四列指标栅格,列起点从 1:2 的 968 / 1418 / 1867 / 2317(间距 450px)压到 1901 / 2117 / 2334 / 2550(间距 216px),压缩率 52%。四列既没换行也没截断,但数字之间的留白从「能喘气」变成「贴面」。要不要开
isDraggable,取决于右栏在 216px 间距下还可不可读,而不是能不能显示出来。
第 4 步:全屏页与路由交接
右栏里点"打开投资笔记"会进 fullScreenPages 配置的页面,实测节点横跨 x=76 … 2758,铺满整个 2800 宽窗口,分栏自动退出。适合放长表格、全屏 K 线、富文本编辑器——这些在 1:2 右栏里都会挤变形。
左栏切换标的时,右栏同步更新(导航模式的定义行为)。实测点第三行"紫金矿业",右栏标题节点从
Text [1066,113,1228,160] "贵州茅台" → "紫金矿业"同步换掉,左栏列表位置不动。这就是 mode: 1 和 mode: 0 的区别:导航模式左栏固定,购物模式右页不断左推、左栏显示次栈顶。购物模式我没测,三折叠上更常见。
真机数据汇总
| 项目 | 实测值 |
|---|---|
| 分栏生效条件 | 窗口 ≥ 600vp 且 宽/高 > 1.2(本机 1318vp、1.522) |
| 1:2 时左栏 / 右栏 | 933px / 1867px,右栏页面 878vp |
| ratio 越界 | 静默钳到边界,无 error/warn |
| ratio + isDraggable | 构建失败 00303038 / failingKeyword: then |
| 拖拽吸附档位 | 933 / 1400 / 1866px 三档 |
| 全屏页 | 1318vp 铺满,isEasySplit() = false |
| 左栏列表行高 | 135px(182 → 317 → 452) |
| 右栏指标列间距 | 1:2 时 450px(968/1418/1867/2317);2:1 时 216px(1901/2117/2334/2550) |
未覆盖:squareWindowMode(方形宽屏,三折叠 M 形态)、enableReducedContainerSize(虚拟容器,会改变页面拿到的窗口宽度口径)、购物模式路由。这三项需要对应形态的设备或改动较大,本轮没有数据,不下结论。





总结
平行视界适合的是"列表 + 详情"这类天然两段式的业务:行情、邮件、IM、订单、商品。它不需要改一行业务代码,一份 JSON 就能让竖屏应用在大屏上不尴尬。
但它只解决"两个页面并排",不解决"一个页面自己会分栏"。右栏内容一旦超过两种宽度形态(比如那四列指标),要么把 isDraggable 关掉锁死比例,要么老老实实做响应式。上表里的 439vp 就是那条分界线。
💡 一句话:
ratio越界是静默钳位的,别信"配了就是生效"——在右栏挂一个onAreaChange读isEasySplit(),10 秒钟就能确认分栏到底有没有起来。
参考文献
- 平行视界(最佳实践):https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-easygo-parallel
- 多设备窗口形态:https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-multi-device-window
- module.json5 配置文件:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/module-configuration-file
- UIContext(isEasySplit)API 参考:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-uicontext-uicontext
- 兼容方案目录:https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-compatible-scheme