一、先看一个常见场景
你做了一个新闻类 App,在手机上跑得好好的。某天产品经理拿了台平板过来,说"这个 App 在平板上跑一下看看"。
一跑,问题来了:整个 App 挤在屏幕中间一条竖栏里,左右两边是大片空白。用户点进一篇文章,文章页占满了整个屏幕——按返回,又回到那个左右留白的列表页。
这种"手机 App 硬塞到大屏"的体验,在平板和折叠屏上非常普遍。
传统的做法是:开发者手动给每个页面写双栏布局。列表在左、详情在右。问题是,一个中型 App 少说几十个页面,每个页面都改一遍,工作量巨大。而且很多团队根本没有平板适配的排期。
华为给了一条系统级的捷径——平行视界(EasyGo)。它的核心思路是:不改业务页面代码,只写一个配置文件,系统自动在宽屏窗口里把你的页面拆成左右两栏。
这篇文章就讲清楚:平行视界到底做了什么、怎么用一个 easy_go.json 让你的 App 在大屏上自动双栏、以及哪些坑会让配置失效。
二、平行视界到底做了什么?
先把概念搞清楚。
平行视界是 HarmonyOS 系统提供的一种窗口显示模式。当你的 App 在平板、折叠屏展开态等宽屏设备上运行时,系统可以把两个页面左右并排显示在一个窗口里——左边是主页,右边是从主页跳转过去的二级页。
举个具体例子。你的 App 有一个新闻列表页,用户点一条新闻跳到详情页。
- 手机上:列表占满全屏,点进去详情也占满全屏,按返回回到列表。这是单页模式。
- 平行视界下:左边显示列表,右边显示详情。用户在左边列表点另一条新闻,右边详情跟着切换。不用返回,左右同时可见。
这本质上是把"页面跳转"变成了"分栏显示"。
系统怎么知道哪些页面该分栏?
这就是 easy_go.json 的作用。你在配置文件里告诉系统:
- 哪个页面是"主页面"(左边栏)
- 从主页面跳到哪些页面时,应该在右边栏打开
- 哪些跳转是普通跳转(不进入双栏)
系统根据这份配置,在宽屏窗口里自动分栏。业务页面的代码一行都不用改。
这跟你自己手写双栏布局有什么区别?区别很大:
| 改动量 | 每个页面都要改布局 | 只写一个配置文件 |
| 适用范围 | 你主动适配的页面 | 配置文件覆盖的所有页面 |
| 系统级能力 | 没有 | 分栏拖拽、拖拽接续、双栏切换都有 |
| 维护成本 | 页面改动要同步改双栏 | 配置文件独立维护 |
| 分栏比例 | 你自己定 | 系统默认左1/3右2/3 |
| 横屏竖屏切换 | 你自己处理 | 系统自动处理 |
当然,平行视界不是万能的。它适合"列表→详情"这种典型分栏场景。如果你的页面布局本身很复杂(比如仪表盘、画布编辑器、视频播放器),还是得手写响应式布局。平行视界的边界是:它适合内容浏览类 App,不适合工具类、创作类 App。
三、最小接入:三步跑通
第一步:声明支持平行视界
在 module.json5 里声明你的 Ability 支持平行视界。具体配置是在 Ability 的 metadata 里加标记:
{
"module": {
"abilities": [
{
"name": "EntryAbility",
"metadata": [
{
"name": "EasyGoClient",
"value": "true"
}
]
}
]
}
}
EasyGoClient 这个 metadata 就是告诉系统:"这个 App 接受平行视界模式"。
第二步:写 easy_go.json
在 entry/src/main/resources/rawfile/ 目录下新建 easy_go.json。这个文件就是平行视界的规则表。
一个最简单的配置长这样:
{
"logicEntities": [
{
"head": {
"function": "com.example.news.ListPage",
"description": "新闻列表页(主页面)"
},
"entities": [
{
"function": "com.example.news.DetailPage",
"description": "新闻详情页(在右侧栏打开)"
},
{
"function": "com.example.news.CommentPage",
"description": "评论页(在右侧栏打开)"
}
]
}
]
}
解释一下结构:
- logicEntities 是一个数组,每个元素代表一组分栏关系。
- head.function 是主页面(左栏)的全路径类名。
- entities 是从主页面跳过去的页面列表,这些页面会在右栏打开。
这个配置的意思是:当用户在 ListPage 里跳转到 DetailPage 或 CommentPage 时,不要全屏跳转,而是在右栏显示这些页面。
这里有个细节要注意:function 的值是页面的全路径类名,不是路由路径。比如你的页面类是 com.example.news.ListPage,那配置里就得写完整的这个字符串。系统是按类名匹配的,不是按路由 URL 匹配的。
第三步:重新编译运行
没有第三步。编译完装到平板上,打开 App,你会发现列表页和详情页自动并排显示了。
这就是平行视界最爽的地方——你写了一个 JSON,业务代码一行没动,系统帮你把分栏做了。

四、配置文件的核心字段
上面那个例子是最小配置。实际项目里你还会用到这些字段。
head:主页面
head 定义哪个页面是左栏主页面。一个 logicEntities 里只能有一个 head。
"head": {
"function": "com.example.MainPage",
"description": "主页面",
"titleBar": {
"background": "#FFFFFF",
"title": "我的App"
}
}
titleBar 可以定制左栏标题栏的样式。如果你不写,系统用默认样式。
entities:右栏页面列表
entities 数组里每个元素是一个从主页面跳转过去的页面。系统检测到你跳到这些页面时,自动在右栏打开。
"entities": [
{
"function": "com.example.DetailPage",
"description": "详情页"
},
{
"function": "com.example.SettingPage",
"description": "设置页"
}
]
特殊跳转规则
有些跳转你不想让它进右栏,想让它全屏打开。比如登录页、支付页——这些页面不适合跟主页面并排。
这时候你可以配置 exception 规则:
"entities": [
{
"function": "com.example.LoginPage",
"description": "登录页(全屏打开)",
"windowMode": "fullscreen"
}
]
windowMode 设为 fullscreen,这个页面就全屏显示,不进右栏。
多组分栏关系
一个 App 可能有多组分栏。比如:
- 新闻模块:列表页 → 详情页(分栏)
- 设置模块:设置主页 → 具体设置项(分栏)
这时候 logicEntities 数组里就有多个元素:
{
"logicEntities": [
{
"head": { "function": "com.example.NewsListPage" },
"entities": [
{ "function": "com.example.NewsDetailPage" }
]
},
{
"head": { "function": "com.example.SettingMainPage" },
"entities": [
{ "function": "com.example.SettingAccountPage" },
{ "function": "com.example.SettingNetworkPage" }
]
}
]
}
每组分栏关系独立,互不影响。
配置文件放哪里?
easy_go.json 放在 entry/src/main/resources/rawfile/ 目录下。这个目录是鸿蒙的 rawfile 资源目录,系统会在运行时自动读取这个文件。
注意:文件名必须是 easy_go.json,大小写敏感。写错文件名系统读不到,分栏不生效,而且不会报错——这是最坑的地方,没有任何提示,你只能自己排查。

五、动态控制:什么时候全屏、什么时候分栏
配置文件是静态的。但有些场景你需要在代码里动态控制——比如用户旋转屏幕时,你想从分栏切到全屏。
申请横屏全屏
平行视界默认在横屏宽屏下生效。如果你想在竖屏也进入全屏模式,可以在 Ability 里动态设置:
import { display } from '@kit.ArkUI';
// 申请横屏,进入全屏显示
display.setOrientation(display.Orientation.LANDSCAPE);
// 申请竖屏,退出全屏
display.setOrientation(display.Orientation.PORTRAIT);
这个 API 跟平行视界配合使用:横屏时系统自动进入分栏模式,竖屏时你手动切回全屏单页。
监听窗口模式变化
如果你想知道当前是不是在平行视界模式下,可以监听窗口模式变化:
import { window } from '@kit.ArkUI';
window.getLastWindow(this.context).then((win) => {
win.on('windowSizeChange', (data) => {
// 判断当前窗口宽度,决定是否做特殊处理
console.info('窗口宽度: ' + data.width);
});
});
实际项目里,你可能需要根据窗口宽度做一些微调。比如在右栏宽度很窄的时候,隐藏某些侧边栏。
分栏模式下的页面生命周期
还有一个容易忽略的点:平行视界模式下,左右两个页面是同时存在的。这意味着左栏的列表页不会被销毁,右栏的详情页加载时,左栏仍然在运行。
这跟手机上单页跳转不一样——手机上跳转到详情页,列表页会被压到栈底,可能被系统回收。但在平行视界下,列表页一直活着。
这带来两个实际影响:
第一,列表页的数据刷新要自己控制。用户在右栏看完详情返回列表,列表页不会自动刷新。如果你需要刷新列表数据,得在右栏页面关闭时主动通知左栏。
第二,两个页面同时持有内存。左栏列表 + 右栏详情,两个页面的状态都在内存里。如果你的页面很重(比如带了大图片缓存),要注意内存占用。
六、五个常见问题
问题 1:配置了 easy_go.json,但分栏不生效
最常见的原因是 function 路径写错了。系统是按类名全路径匹配的,你写的路径必须跟页面的 @Entry 类名完全一致。
排查方法:把你配置文件里的 function 值跟实际工程里的页面路径逐个对一遍。多一个点、少一个字母都不行。
问题 2:右栏页面跳转后,左栏也变了
这通常是因为你在右栏页面里做了某种全局跳转,导致整个窗口的主页面被替换了。
平行视界的规则是:右栏页面的跳转如果不在 entities 列表里,系统会把它当成普通跳转,可能影响左栏。
解决方法:把所有可能的右栏跳转路径都列在 entities 里,或者在右栏页面里用 router.pushUrl 而不是 router.replaceUrl。
问题 3:分栏后左右比例不协调
系统默认左栏约占 1/3,右栏约占 2/3。如果你想调整比例,目前平行视界不支持配置左右比例。这是系统级行为,开发者无法自定义。
问题 4:竖屏手机上也想分栏
平行视界的触发条件是"宽屏窗口"。竖屏手机宽度不够,系统不会自动分栏。如果你强制在竖屏上分栏,左右两栏都很窄,体验很差。
建议:竖屏保持单栏,横屏或折叠屏展开态才进分栏。
问题 5:分栏状态下,返回键行为混乱
用户在右栏按返回,预期是"右栏关掉,回到左栏"。但有些 App 里返回键会把整个窗口关掉。
这是因为你的返回逻辑没有区分窗口模式。正确的做法是:在平行视界模式下,返回键应该先关右栏,右栏关完再关左栏。
具体怎么判断当前是不是平行视界模式?你可以通过窗口宽度来判断。宽度大于某个阈值(比如 600vp)就认为是分栏模式,返回键先关右栏;否则就是单栏模式,正常返回。
问题 6:配置了多个 logicEntities,互相干扰
有时候你会发现,配置了多组分栏后,A 组的页面跳转到 B 组的页面时,系统行为不符合预期。
这是因为平行视界的匹配是按"当前 head 是谁"来判断的。如果你从 A 组的 head 页面跳到 B 组的 entities 页面,系统不知道该把这个跳转归到哪一组。
解决方法:确保从一个 head 页面跳转出去的所有路径,都在同一个 logicEntities 组里。跨组跳转的页面,单独配 windowMode: fullscreen。

七、小结
平行视界是 HarmonyOS 给大屏适配的一条捷径。核心要点:
平行视界不是替代手写响应式布局的方案,它是给那些"来不及做大屏适配但又不想在平板上显示一条竖栏"的 App 准备的兜底方案。适合列表型、内容型 App。
什么时候该用、什么时候不该用
最后做个判断,帮你决定平行视界适不适合你的 App。
适合用平行视界的场景:
- 新闻、资讯、社交类 App:典型的列表→详情结构
- 电商类 App:商品列表→商品详情
- 设置类页面:设置主页→具体设置项
- 内容浏览类:章节列表→阅读页
不适合用平行视界的场景:
- 工具类 App:计算器、备忘录、画板——单页就完成全部操作,分栏没意义
- 游戏类:全屏体验,分栏破坏沉浸感
- 视频播放器:全屏播放,分栏会让画面变小
- 复杂仪表盘:多图表、多控件,需要手写响应式布局
简单判断:你的 App 主要是"看内容"还是"做操作"?看内容的,平行视界收益大;做操作的,老老实实手写布局。
留个小任务
本文基于 HarmonyOS 7 API 编写,示例配置可直接在 DevEco Studio 中运行。



