出门拍夜景之前,先翻翻拍摄手册:看完建筑的透视处理,再对照夜景的曝光建议,偶尔点开照片看一下细节。这些动作不复杂,却一直在正文和目录之间来回发生。

换到展开的折叠屏,屏幕宽了,操作却未必跟着改变。如果正文仍然整页打开,想读另一篇时还是得先返回目录。大屏装下了更多文字,却没有减少查阅过程里的来回跳转。

平行视界值得讨论的地方就在这里。它可以让两个页面在同一个应用窗口中并列出现,把频繁切换的关系留在眼前。能不能让多出来的空间真正帮到用户,取决于哪些页面需要同时存在,以及跳转、全屏和返回怎样连接。

一、先看页面之间的关系,再看屏幕宽度

官网将平行视界定位为一种系统级兼容方案:通过标准化配置,让原本未适配分栏的应用在宽屏窗口中同时显示两个页面。开发者自配置从 API23 开始支持,API26 进一步开放显式路由模式、分栏比例与拖拽等配置。官方开发指南

所以,眼前的左右两块区域应当来自明确的页面关系。导航模式保留左侧主页,让后续内容在右侧切换;购物模式则在继续打开下一页时,把原来的右页推向左侧。官方能力解读

这两种模式服务于不同的浏览习惯。查手册、读邮件时,人需要不断回到稳定入口;逐级浏览商品或推荐内容时,人更在意前后两层内容的接续。选择模式之前,先想清楚用户反复切换的是哪两件事。

“城市影像手册”采用导航模式:目录承担稳定入口,正文承担读取内容,大图承担仔细观察。它沿用同一套页面,通过配置改变页面的组织方式。

二、同一本手册,先体验没有分栏的时候

关闭 entry 模块的 easyGo 引用,在展开窗口里打开夜景篇。详情占满整个应用,目录暂时消失。想看建筑篇,需要返回目录,再打开建筑。

关闭平行视界:先返回目录,再打开另一篇

这里没有操作错误,也没有刻意安排复杂流程,它就是常见的单页路由。对于一次只看一篇的阅读,这条路径完全可以成立;问题出在持续对照时,目录成为了需要反复找回的入口。

保持同一个模拟器、相同的图片与页面代码,重新开启平行视界。启动后目录与关联页并列,再点夜景、点建筑,右侧正文连续切换,左侧目录持续保留。

开启平行视界:目录保持,右侧连续切换正文

对查阅任务来说,节省的不只是一个返回动作。用户还能看到还有哪些内容、自己选中了哪一篇,下一次选择发生在原来的位置。目录从临时入口,变成了阅读过程中一直可用的导航。

上述两版都在 HarmonyOS 7.0.0.107 / API26 公版三折叠模拟器的3184×2232展开窗口重新采集。应用编译后的业务字节码一致,区别来自配置;画面外的步骤栏与点击圆圈是后期标注。

三、两个页面能并列,也需要适时让出整块空间

查手册时看正文和观察照片,是两种不同的任务。读文字需要快速换篇,看建筑线条却可能需要更完整的图像区域。始终保留目录,也会占用这个时刻的观察空间。

本例把照片页配置为全屏页。先在建筑正文滑动一小段,记住“01 选定视觉中心”的位置,再点击“查看大图”。图片页占满窗口;返回正文后,目录和详情重新并列。

打开全屏图片,返回双栏并恢复正文位置

这一段要分开看两个结果:系统配置负责退出和恢复分栏,应用负责保存当前条目与阅读偏移。完整 Demo 的稳定状态日志记录了 true → false → true;同一段落顶部返回前后都在894px。

这也是接入中容易漏掉的细节。用户说“继续刚才的阅读”,既包含页面回来了,也包含内容位置还在。只完成前一半,虽然能展示双栏,查阅过程依然会被打断。

四、把场景中的三个角色,写成页面配置

从文末仓库导入“城市影像手册”,按 README 配置个人签名。页面已经准备好:Index 是目录,Guide 是启动关联页,Detail 是用户选择后的正文,Photo 是完整图片。两种正文入口共用一个阅读组件。

1. 先运行同一套页面

在 DevEco Studio 打开仓库根目录,同步依赖并配置个人签名,选择符合平行视界条件的 API26 大屏设备或模拟器。先按 README 构建 single 版本,体验“目录 → 正文 → 返回目录 → 另一篇”;随后构建 parallel 版本,在相同窗口形态下对照。

配置文件放在 entry/src/main/resources/base/profile/easy_go.json。module.json5 的 module 对象增加引用:

"easyGo": "$profile:easy_go"

路由页需同时注册在 entry/src/main/resources/base/profile/main_pages.json:

{"src": ["pages/Index", "pages/Guide", "pages/Detail", "pages/Photo"]}

2. 声明主页、关联页与全屏页

在 entry/src/main/module.json5 引用 easy_go.json 后,核心配置如下:

{
  "common": {
    "displayModeOptions": {
      "wideWindowMode": "routerSplit",
      "squareWindowMode": "routerSplit",
      "routerSplitOptions": {
        "homePage": "pages/Index",
        "relatedPage": "pages/Guide",
        "fullScreenPages": ["pages/Photo"],
        "enableReducedContainerSize": true,
        "mode": 1,
        "wideSplit": { "isDraggable": true },
        "squareSplit": { "isDraggable": true },
        "supportLandscapeFullscreen": true
      }
    }
  }
}

homePage 保留目录,relatedPage 提供启动时右侧的内容;mode: 1 明确选择导航模式;fullScreenPages 为照片页保留临时全屏的路径。enableReducedContainerSize 开启虚拟容器能力,页面本身也要按分栏后的实际空间排版。

如果想亲自验证前后区别,工程根目录的切换脚本只增加或移除 easyGo 引用:

./set-view-mode.ps1 -Mode single
./build-app.ps1

# 安装并体验单页流程后,再构建另一种配置。
./set-view-mode.ps1 -Mode parallel
./build-app.ps1

每次都需重新构建、安装和启动,因为修改的是模块配置。保持设备与窗口形态一致,才能把注意力放在这项能力带来的变化上。

五、目录照常打开详情,系统负责组织页面

目录点击并不需要改成手写两个区域。entry/src/main/ets/pages/Index.ets 仍然通过正常 Router 跳转到详情:

import { router } from '@kit.ArkUI';
import { HandbookStore } from '../HandbookStore';

// 放在 Index 组件内,目录行通过 this.open(entry.id) 调用。
private async open(id: string): Promise<void> {
  HandbookStore.select(id);
  HandbookStore.flush();
  try {
    await this.getUIContext().getRouter().pushUrl(
      { url: 'pages/Detail', params: { id: id } },
      router.RouterMode.Single
    );
  } catch (error) {
    this.getUIContext().getPromptAction().showToast({
      message: '内容暂时无法打开,请重试'
    });
  }
}

共享选中 ID 让目录高亮与正文保持一致,RouterMode.Single 避免连续选择时反复堆积同名详情路由。启动关联页不依赖目录点击后的参数,而是读取默认或上次保存的阅读状态。

3. 正文读取当前条目,全屏前保存位置

在 pages/Detail.ets 中读取路由参数,交给 HandbookStore 与阅读组件。下列代码沿用工程中的共享存储和 HandbookReader,列表高亮、正文与偏移都指向同一个条目。

import { handbookStorage, HandbookStore } from '../HandbookStore';
import { HandbookReader } from '../HandbookReader';
import { ParallelTrace } from '../ParallelTrace';

@Entry(handbookStorage)
@Component
struct Detail {
  onPageShow(): void {
    HandbookStore.activeReader = 'Detail';
    const params = this.getUIContext().getRouter().getParams()
      as Record<string, string>;
    if (params && params['id']) {
      HandbookStore.select(params['id']);
    }
    HandbookStore.resume();
    ParallelTrace.pageShown(this.getUIContext(), 'Detail');
  }

  onPageHide(): void { HandbookStore.flush(); }

  build() {
    Column() {
      HandbookReader({ showBack: true })
    }.width('100%').height('100%')
  }
}

HandbookReader 的照片入口在跳转前保存阅读位置。点击正文图片时调用 openPhoto():

private async openPhoto(): Promise<void> {
  if (!this.restoring) {
    HandbookStore.setOffset(
      this.selectedId, this.scroller.currentOffset().yOffset
    );
  }
  HandbookStore.flush();
  try {
    await this.getUIContext().getRouter().pushUrl({
      url: 'pages/Photo', params: { id: this.selectedId }
    });
  } catch (error) {
    this.getUIContext().getPromptAction().showToast({
      message: '图片暂时无法打开,请重试'
    });
  }
}

照片页则沿用普通跳转与返回,只是在配置中声明它应全屏展示。进入前保存偏移,返回后恢复该篇位置,仍由 HandbookStore 与 HandbookReader 完成。系统组织页面的职责清楚以后,业务代码也更容易保持单一来源。

4. 按操作核对系统状态

安装 single 和 parallel 时分别清空日志并记录操作。仓库的跟踪点会在页面出现及稳定后输出 isEasySplit(),配合画面判断:single 中目录、详情均为 false;parallel 中关联页与详情为 true,照片页为 false,返回详情重新为 true。

hdc list targets
# 改为上一条命令列出的目标设备。
$device = '127.0.0.1:5555'
hdc -t $device shell hilog -x -T HandbookFlow
# single:关闭 easyGo
page=Index phase=settled split=false
page=Detail phase=settled split=false

# parallel:开启 easyGo
page=Guide phase=settled split=true
page=Detail phase=settled split=true
page=Photo phase=settled split=false
page=Detail phase=settled split=true

先连续选择两篇正文,左侧目录应保持;再向下阅读并打开照片,照片应全屏,返回后继续原段落。画面和日志共同核对,能避免仅凭页面排成两列就判断系统分栏已生效。

具体页面实现、全屏入口、分栏日志以及完整对照步骤,保留在配套工程与 README。不需要一次读完所有状态代码,也能先把三个场景跑通。

六、栏宽可调之后,仍要照顾真实的阅读空间

API26 的系统分隔线支持三档吸附比例。正文变宽时,一行能容纳更多文字;变窄时,照片和文字都应留在本栏,而不是沿用整个窗口的固定宽度。

正文较宽时的实际分栏布局

正文较窄时的实际分栏布局

这两张是松手后的实际结果。本轮模拟器连续采样在拖动时仍有空白/模糊占位,松手后恢复,因此没有用剪去过渡的动图来评价动画流畅度。布局落位可读与过渡自然,是需要分别观察的两件事。

设备与窗口条件也决定配置能否生效。普通直板手机横屏不等于符合平行视界条件,配置里写了宽窗口和方形窗口,也不代表所有形态都已验证。实际接入应结合官方条件、isEasySplit() 状态和运行画面判断。

七、屏幕宽了以后,值得留下来的是什么

回到夜景拍摄前的手册查阅,最有用的变化是目录不必反复找回、正文可以连续对照、照片需要时能占满窗口。多出来的空间被分配给了当前任务,而不只是让所有元素变大。

对于已有应用,平行视界提供了通过配置重新组织页面的途径。是否适合自己的产品,先看用户最常在什么页面之间往返,哪个入口需要稳定保留,哪些内容应该临时独占空间。把这些关系想清楚,双页并列才有明确的用途。

配套工程与资料

GitCode:城市影像手册 Demo。README 包含个人签名、配置切换、三组操作与日志复核步骤,示例正文和照片随工程提供。

平行视界开发指南 · HarmonyOS 7 平行视界能力解读

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐