HarmonyOS 7 平行视界 EasyGo 实战:一份 JSON 让邮件应用在大屏自动分栏
邮件、资讯、购物、IM 这一类的应用,手机上是列表点进详情,到了平板和折叠屏展开态还这么用,右边空着一大块,体验立刻显得敷衍。改吧,又是大工程:列表页要加选中态、详情要做成右栏、窗口宽度要监听、折叠切换要处理状态保持——一个"左右分栏"四个字,够写一整个迭代。
HarmonyOS 7 里有另一条路:平行视界 EasyGo。它不让你写分栏代码,而是在模块里放一份 JSON 声明"哪个页面当左栏、哪个页面当右栏",系统在窗口层把分栏做掉。应用代码一行不改,邮件应用在平板上打开就是左列表右正文,折叠屏展开自动跟着分,合上又自动回到单栏。
一、EasyGo 在哪一层做事
先把概念放准。EasyGo 是窗口级的兼容方案:系统读到你声明的配置后,把应用的窗口切成左右两个容器,左容器渲染你指定的首页,右容器渲染配对页。你的页面代码对此毫无感知——它还是那个普通的 @Entry 页面,用 router.pushUrl 正常跳转,系统决定这个跳转落在左栏还是右栏。
这套机制天生适合"列表→详情"的浅路由结构:邮箱、新闻、电商、文档库。深导航、复杂状态机的应用,EasyGo 的两栏模型兜不住,那是 Navigation 组件分栏(应用层自己做)的领地。
和 Navigation 分栏的边界后面选型一节细说,先记住一句话:EasyGo 零代码但能力受配置约束,Navigation 全可控但要重写页面结构。
二、三步接入:两份文件,零业务代码
完整的接入就三步。第一步,写配置文件 entry/src/main/resources/base/profile/easy_go.json:
{
"common": {
"displayModeOptions": {
"wideWindowMode": "routerSplit",
"routerSplitOptions": {
"homePage": "pages/erqi/easygo/MailListPage",
"relatedPage": "pages/erqi/easygo/MailDetailPage",
"fullScreenPages": ["pages/erqi/easygo/MailSettingPage"],
"transPages": ["pages/erqi/easygo/MailComposePage"],
"mode": 1,
"wideSplit": { "ratio": "1 | 2" }
}
}
}
}
第二步,在 module.json5 的 module 对象里加一行,指向这份配置:
"easyGo": "$profile:easy_go"
注意这是 module 级属性,和 "pages": "$profile:main_pages" 平级。网上有资料写成 metadata 数组挂 ohos.easygo.window,至少在 API 26 的模块校验 schema 里不是这个形态——以本地 SDK 的 modulecheck schema 为准,就是 easyGo 字符串属性。
第三步,没有了。页面代码就是你平时写的 router 页面:列表页 router.pushUrl 到详情页,详情页 router.getParams() 拿参数,和手机上没有任何区别。
我在平板上装起来第一眼的效果:应用启动直接就是分栏,左栏收件箱占三分之一,右栏预载了详情页的空态(“未选择邮件"提示);点左栏任意一封邮件,右栏立刻换成那封邮件的正文。整个过程列表页代码里没有一行"选中”"右栏"相关的逻辑——选中态的高亮是列表页本来就有的,右栏换内容是系统做的。


三、配置结构:设备分节与窗型分派
easy_go.json 的结构是两层。顶层按设备类型分节:common(全设备默认)、phone、2in1、tablet,具体节里的配置覆盖 common——想给平板 1:2、给 2in1 电脑 1:1,就分节写。
第二层是 displayModeOptions,按窗口形态分派,这是理解 EasyGo 的关键:
| 键 | 管什么 | 取值 |
|---|---|---|
| wideWindowMode | 矩形横屏窗口(平板横持、2in1) | routerSplit / navigationSplit / original |
| squareWindowMode | 方形窗口(三折叠 M 态、双折叠屏展开态) | routerSplit / navigationSplit / original |
original 是"不动,保持手机样式"。选了 routerSplit 就必须给 routerSplitOptions,选 navigationSplit 就必须给 navigationSplitOptions,两栏都 original 则其它键都不许出现——这份 schema 的约束写得非常严,字段名拼错、结构不符,构建期直接报错,不会静默失效。
方形窗口要单独配,这是最容易踩的第一个坑。 我实测时先只配了 wideWindowMode,在折叠屏展开态(2210×2416 的近方窗口)打开应用——不分栏,列表横贯全屏。方形窗口归 squareWindowMode 管,没配就当没这回事。补上 "squareWindowMode": "routerSplit" 之后,展开态立刻分栏。如果你只在大屏平板上测过就收工,折叠屏用户拿到的是没分栏的版本,而且没有任何报错提示你去查。


窄窗口不用配:手机竖屏、折叠屏合拢的外屏,EasyGo 自动按 original 处理,我在手机竖屏上实测列表就是正常单栏,无需任何声明。
四、routerSplitOptions:每个字段干什么
这是配置的主体,逐字段过一遍(以下行为全部实测验证):
homePage / relatedPage:分栏态的左栏页与右栏页,值是 router 页面路径。启动时系统左栏加载 homePage、右栏预载 relatedPage——注意是预载,右栏一开始就是活的页面(我这里是详情页的空态),不是一张静态占位图。
transPages:只在右栏显示的过渡页。我把写邮件页放进去,实测点「写信」后左栏列表纹丝不动,写信页整个出现在右栏。适合"从详情再进一步"的轻操作页:回邮件、加备注、改标签。
fullScreenPages:不参与分栏的整屏页。设置页放进去后,点「设置」整个窗口被设置页铺满,分隔线消失。实测有个好细节:从分栏态进设置页再返回,系统恢复的是之前的分栏快照——左栏还是列表,右栏还停留在你进设置前看的那封邮件。整屏页是压栈的,不打散分栏状态。
pagePairs:细粒度的页面配对(from→to),适合左右栏关系不是"一个列表对一个详情"的多入口场景,比如从搜索页进详情、从收藏页进详情,各自配对。
mode:0 购物模式 / 1 导航模式,默认 1。官方 schema 对这两个模式只有一句枚举说明。我在 home+related 的基础结构下把 0 和 1 都跑了一遍:启动分栏、左点右出、返回行为完全一致,差异没有在这个简单结构里显现。这个值更适合 pagePairs 多配对的复杂场景,基础接入按默认 1 即可,不必纠结。
enableInSplitScreen:应用本身处于系统分屏(两应用并排)时还做不做应用内分栏,默认 false。想想也是对的:分屏里窗口已经不大,再左右分栏每栏就剩一条了。
五、分栏比例:ratio 的格式坑与拖拽
比例配置在 wideSplit / squareSplit 里,两种窗型各配各的——这也是实测里很讨喜的设计:平板宽屏 1:2、折叠屏方窗 2:3,互不干扰。
ratio 的格式是 "1 | 2":数字、空格、竖线、空格、数字。 schema 里的正则是 ^[1-9]\d* \| [1-9]\d*$,空格是格式的一部分,写 "1|2" 或 "1 : 2" 都过不了校验。实测配置 "1 | 2" 在 2880 宽的平板上,分隔线精确落在 960px;换成 "2 | 1" 重装,左栏立刻翻转为三分之二。
isDraggable 与 ratio 互斥。想让用户拖动分隔线,就把 ratio 去掉只写 "isDraggable": true——同时写两个会被 schema 的条件约束拒绝。开启后默认 1:1,用户可以在 1:2、1:1、2:1 三档之间拖动。
拖拽还有一个排查比例问题时值得知道的行为:用户拖过分隔线后,系统会记住这次拖动。我实测把 isDraggable 版本换成固定 ratio 的版本重新安装,分隔线停在 1:1 中位而不是新配置的 1:2,重启应用无效,最后卸载重装才回到配置值。也就是说调试比例配置时,如果之前拖过线,看到的位置可能不是配置值——先卸载重装排除残留状态,再判断配置是否生效。
实测拖拽还有个自动化测试层面的细节:注入的拖动动作要够慢才被识别为拖 divider。900 毫秒的快速滑动毫无反应,2000 毫秒的慢拖精确生效,分隔线从 1:1 的中点一路拖到 1:2 的位置,两侧内容跟着重排。手工拖没这个问题——这提醒的是写 UI 自动化的人,不是写应用的人。
splitDividerColor 给分隔线配色,light/dark 各一个,8 位十六进制(#RRGGBBAA)。我配了红色做验证,截图逐像素看,分隔线位置确实是红色系像素——配置真实生效,不是摆设。




六、折叠屏实测:展开分栏、合拢单栏、切换实时
折叠屏是 EasyGo 的主场,把 Mate X71 的三个状态全测了:
| 状态 | 窗口 | 实测行为 |
|---|---|---|
| 合拢(外屏) | 1080×2444 窄窗 | 自动单栏,无需配置 |
| 展开(大方窗) | 2210×2416 | squareWindowMode 接管,按 squareSplit 2:3 分栏 |
| 合拢↔展开切换 | — | 实时跟随:展开瞬间分栏回来,比例保持 2:3 |
第三行值得强调:窗口形态变化时系统自己重排,应用收不到也不需要处理任何"折叠事件"。这就是声明式的红利——形态逻辑全部在系统侧,你声明的是意图(“这种窗型下我要分栏”),执行细节不用管。

七、返回行为的两个细节
分栏态下的路由返回有两个实测发现,接交互预期时要心里有数。
其一,fullScreenPage 的返回是干净的:整屏页弹出,分栏快照原样恢复(前面说过)。其二,右栏内容页里调 router.back(),右栏会被清空——不是回到预载占位,是整个右栏内容消失,只剩左栏列表待在原地,再点任何一封邮件右栏才回来。也就是说"回到列表"这个按钮在分栏态下的观感是"关掉右栏",而不是"回到初始分栏"。行为本身自洽,但如果你预期 back 后右栏还挂着占位页,实际不是——要么接受,要么把详情页里的返回按钮在分栏态下做别的处理(比如换一封默认邮件)。
八、选型:EasyGo、手写一多、Navigation 分栏
三条路放在一起,按投入和产出选:
| 方案 | 投入 | 能力边界 | 适合 |
|---|---|---|---|
| EasyGo | 一份 JSON | 两栏结构、配置项内的定制 | 存量应用、列表-详情浅路由 |
| 手写一多(断点/栅格) | 重写布局 | 任意自定义 | 对大屏有定制设计的应用 |
| Navigation 分栏 | 引入 Navigation 体系 | 多栏、自定义转场 | 新项目、复杂导航 |
我的建议按场景分:应用已经是稳定的 router 结构、想低成本把大屏体验从"能用"提到"体面",EasyGo 是性价比最高的一步,一个下午能上线;新起的应用直接上 Navigation,应用层分栏的自由度值得;对大屏有强定制设计(沉浸式画廊、双栏编辑器)的,老老实实手写一多。三者不互斥——EasyGo 兜底 + 个别页面 fullScreenPages 摘出来自定义,是常见的混合用法。
九、接入清单
把全部实测结论收成一张上手清单:
- 配置文件放
resources/base/profile/easy_go.json,module.json5 的 module 里加"easyGo": "$profile:easy_go"(与 pages 平级,不是 metadata)。 - 顶层按设备分节(common/phone/2in1/tablet),具体覆盖通用。
- 折叠屏展开态记得配 squareWindowMode,只配 wideWindowMode 方窗不分栏。
- ratio 格式
"1 | 2"(空格是格式的一部分);要可拖拽就去掉 ratio 只写 isDraggable,二者互斥。 - 宽窗方窗比例各自独立:wideSplit 管横屏、squareSplit 管方窗。
- 设置页这类无关页面进 fullScreenPages,写邮件这类轻操作页进 transPages。
- schema 严格校验(additionalProperties: false),字段拼错构建期报错——报错是好事,对着 schema 改就行。
- 右栏 back 会清空右栏,交互预期按"关掉右栏"理解。
- 窄窗口(手机/折叠合拢)自动单栏,无需配置。
更多推荐



所有评论(0)