HarmonyOS 7 EasyGo + ArkUI Navigation:平行视界导航模式的列表选中态、右栏路由栈与全局返回收口【鸿蒙心迹】

这次不写"平行视界怎么打开",而是把问题往真实阅读类 App 里再推进一步:左边文章列表固定,右边详情可以继续点相关推荐;当右栏已经压入第二层详情以后,用户执行全局返回,应该只退右栏,不应该把左边选中的文章一起重置。

我给这次 Demo 起名叫 ParallelReaderLab,页面叫 Article Desk。

为了让正文、日志和图片完全对得上,这次固定一组运行状态:

  • Mode:NAVIGATION
  • Split Ratio:1:2
  • Selected:article_20261001_12
  • Right Stack:2 → 1
  • Related:related_20261001_03
  • Last Back:2 → 1

HarmonyOS 7(API 26)的平行视界当前提供购物模式和导航模式两类典型交互。导航模式的特点很适合阅读、邮件、IM 这类场景:左侧导航或列表保持稳定,右侧内容区继续切换;官方说明里也特别强调了全局返回时右侧页面按层级回退,以及 1:1、1:2、2:1 等分栏比例。

我这篇真正关心的不是"页面能不能分成左右两块",而是三个状态能不能拆开:

左栏选中态、右栏路由栈、系统平行视界布局状态。

一、先把"左边选中了谁"从右栏页面里拿出来

最开始的实现很普通。

点击列表项以后,直接 push 一个详情页:

ts 复制代码
private openArticle(articleId: string): void {
  this.navPathStack.pushPathByName('ArticleDetail', {
    id: articleId
  })
}

手机单栏时看不出问题。

但进入平行视界以后,左边列表和右边详情同时存在。此时 ArticleDetail 如果自己维护"当前文章 ID",左边 ListItem 又维护一份 selected 状态,两边很快就会漂移。

例如左边已经点中 article_20261001_12,右边详情继续点相关推荐 related_20261001_03。此时右栏路由已经两层,但左栏仍然应该高亮原始入口文章,而不是突然跟着右栏相关推荐切换。

所以第一步,我把左栏入口状态单独放进一个轻量状态模型。

ts 复制代码
export class ArticleRouteState {
  selectedArticleId: string = 'article_20261001_12';
  rightDepth: number = 1;

  selectFromLeft(articleId: string): void {
    this.selectedArticleId = articleId;
  }

  updateDepth(depth: number): void {
    this.rightDepth = depth;
  }
}

这里的关键不是这个类有多复杂,而是它明确了一件事:

selectedArticleId 描述的是左栏当前入口,不等于右栏栈顶页面 ID。

这两个值在第一层详情时可能相同,进入相关推荐以后就不一定相同。

如果一开始把两个概念混成一个变量,后面处理返回时一定会出问题。

二、Navigation 只管理右栏层级,不替左栏决定高亮项

平行视界官方示例本身就是基于 Navigation 路由来承载应用页面关系。

我这次也沿用这个方向,但给右侧单独维护 NavPathStack。

这段代码解决的问题,是左栏点击文章以后更新入口状态,同时把右侧详情放进路由栈。

ts 复制代码
@Entry
@Component
struct ArticleDesk {
  private navPathStack: NavPathStack = new NavPathStack();

  @State private selectedArticleId: string =
    'article_20261001_12';

  private openFromLeft(articleId: string): void {
    this.selectedArticleId = articleId;

    this.navPathStack.clear();
    this.navPathStack.pushPathByName(
      'ArticleDetail',
      { id: articleId }
    );

    console.info(
      `Selected: ${articleId}, depth=1`
    );
  }

  build() {
    Row() {
      ArticleList({
        selectedId: this.selectedArticleId,
        onSelect: (id: string) => {
          this.openFromLeft(id);
        }
      })

      Navigation(this.navPathStack) {
        ArticleEmpty()
      }
    }
  }
}

我在"左栏重新选择文章"时会清掉右栏原来的相关推荐层级。

这是一个产品判断。

如果用户已经在文章 A 的相关推荐第三层,此时直接从左边点文章 B,我希望右边重新从 B 的第一层详情开始,而不是让 B 接在 A 的旧路由链后面。

正式项目当然也可以保留历史,但必须把行为定义清楚。

三、右侧继续点相关推荐时,不改左侧 selected

接下来就是这次最核心的边界。

article_20261001_12 的详情页里又点击了 related_20261001_03。

这段代码只做右侧 push:

ts 复制代码
private openRelated(relatedId: string): void {
  this.navPathStack.pushPathByName(
    'RelatedDetail',
    { id: relatedId }
  );

  console.info(
    `Route push: ${relatedId}, ` +
    `depth=${this.navPathStack.size()}`
  );
}

此时运行状态变成:

text 复制代码
Selected: article_20261001_12
Right top: related_20261001_03
Right depth: 2

注意,我没有写:

ts 复制代码
this.selectedArticleId = relatedId

因为相关推荐是右栏内部的继续阅读,不是用户从左栏重新选择入口。

这个区别放到小屏手机里没那么明显,但进入平行视界以后,左边一直在用户眼前,高亮错一次就会非常突兀。

四、全局返回真正应该回的是右栏层级

HarmonyOS 7 平行视界导航模式的一个重要体验,就是全局返回与右侧页面层级保持一致。

在我的业务状态里,这意味着:

depth = 2 时返回,右栏从相关推荐回到入口文章;左栏 selected 不变。

我给页面内部的路由收口写成一个方法,方便调试和测试。

ts 复制代码
private backRightPane(): boolean {
  const before = this.navPathStack.size();

  if (before <= 1) {
    return false;
  }

  this.navPathStack.pop();

  const after = this.navPathStack.size();

  console.info(
    `GlobalBack: depth ${before} -> ${after}`
  );
  console.info(
    `Selected kept: ${this.selectedArticleId}`
  );

  return true;
}

这段代码不是用来"替代系统平行视界返回机制"。

系统侧是否触发分栏回退、页面如何呈现,仍然由当前 EasyGo / 平行视界配置和 Navigation 路由共同决定。

我这里做的是业务收口:右栏路由变化以后,明确保证左栏入口状态不被顺手重置。

最终这次日志固定为:

text 复制代码
EasyGo mode: NAVIGATION
Split ratio: 1:2
Selected: article_20261001_12
Route push: related_20261001_03 depth=2
GlobalBack: depth 2 -> 1
Selected kept: article_20261001_12

DevEco 图里我特意让四块信息同时出现。

左边是 ParallelReaderLab 工程结构,中间是 ArticleDesk.ets,右边模拟器已经处于平行视界阅读状态,底部 HiLog 则直接验证 depth 2 → 1 以后 selected 仍然没变。

五、为什么我没有把平行视界配置 JSON 大段贴出来

这次我刻意没有把文章写成 EasyGo 配置字段说明书。

原因是 HarmonyOS 7 官方当前已经提供专门的平行视界示例,示例以 Navigation 路由的购物类应用为载体,覆盖主页与关联页、路由模式、过渡页面、全屏请求、横屏请求、虚拟容器以及应用内分屏等典型场景。

这些配置应该直接以当前 SDK 和官方样例为准。

本文更想处理的是配置完成以后,应用内部仍然需要自己维护的业务状态。

很多适配问题并不是 EasyGo 没生效,而是:

  • 左栏 selected 跟右栏栈顶绑定;
  • 每次路由变化都刷新整份列表;
  • 返回时把详情和入口状态一起清空;
  • 大屏有双栏,小屏又维护另一套完全不同的数据模型。

真正难维护的是这些。

六、1:2 分栏只是显示策略,不应该写进业务逻辑

这次 Demo 固定使用 1:2。

左边是文章目录,右边是长文阅读,右侧需要更大的内容宽度,所以这个比例比较自然。

HarmonyOS 7 平行视界当前也支持 1:1、1:2、2:1 等比例以及拖拽调整。

但我不会在业务层写:

ts 复制代码
if (ratio === '1:2') {
  // selected 逻辑
}

比例只应该影响布局。

文章是谁、右栏深度多少、返回以后哪个 ID 继续高亮,不应该因为用户拖了一下分割线就重新计算。

这也是我现在判断多设备适配质量的一个标准:

视觉状态变化可以很多,业务状态变化要尽量少。

七、最终运行图验证的不是"左右两栏",而是返回以后状态没乱

最终手机 / 折叠屏运行图固定在一次全局返回之后。

此时调试卡片里显示:

text 复制代码
Mode: NAVIGATION
Ratio: 1:2
Right depth: 1
Selected: article_20261001_12
Last back: 2 -> 1

右边已经从 related_20261001_03 回到 article_20261001_12 的正文。

左边高亮仍然是同一篇文章。

如果只看"页面是不是双栏",这个 Demo 十几分钟就能搭出来。

真正跑通以后,我反而更在意这几次操作:

  1. 左栏连续点三篇文章;
  2. 右栏连续进入两级相关推荐;
  3. 全局返回两次;
  4. 调整分栏比例;
  5. 再从左栏选择新文章。

每一步都检查 selected、right depth 和当前页面 ID。

只要三者能一直对上,平行视界才不是"把两张页面摆在一起"。

八、这次真正留下的是两套互不抢状态的导航

写到最后,我把整个页面理解成两套层级。

第一套是左侧入口选择:

text 复制代码
article_20261001_12

它代表用户从目录里选中了什么。

第二套是右侧内容路由:

text 复制代码
ArticleDetail
  └── RelatedDetail

它代表用户在当前入口里读到了哪一层。

EasyGo / 平行视界负责把这种关系变成适合大屏的系统级分栏体验,Navigation 负责页面路由,而应用自己要保证两套状态不要互相覆盖。

以后如果继续扩展,我会再补两个测试:

  • 全局返回到 depth=1 后再返回,验证是否退出当前详情或回到上一级业务入口;
  • 从平行视界切回单栏以后,验证同一份 selected 和 right path 能否继续使用。

这两项比再调一个漂亮分栏比例更值得做。

参考资料

相关推荐
李游Leo1 小时前
HarmonyOS 7 + ArkUI Accessibility:闪控窗紧凑态的语义树裁剪与读屏焦点收敛【鸿蒙心迹】
华为·harmonyos
李游Leo2 小时前
HarmonyOS 7 ArkUI Navigation + WindowStage:折叠屏窗口宽度驱动的单双栏切换与草稿状态保持【鸿蒙心迹】
华为·harmonyos
HwJack202 小时前
【共创稿事节】HarmonyOS 7材质与光照:真实感的营造与性能的平衡
pytorch·harmonyos·材质
翼辉cto2 小时前
otlinx-datetime 官方 KMP 日期时间库的 OpenHarmony 鸿蒙化适配实战
华为·harmonyos
m0_738185822 小时前
Flutter 鸿蒙化实战:flutter_iot_wifi 适配 OpenHarmony,IoT 设备 WiFi 配网
物联网·flutter·华为·harmonyos·鸿蒙
TMT星球3 小时前
华为Mate 90系列及全场景新品发布会举行,多款重磅新品亮相
华为
李游Leo3 小时前
HarmonyOS 7 + Hvigor-module.json5:权限增量审计与隐私声明一致性门禁【鸿蒙心迹】
数码相机·华为·harmonyos
李游Leo3 小时前
HarmonyOS 7 ArkTS + Node.js:把权限声明、运行时授权与隐私文案做成提审前一致性扫描【鸿蒙心迹】
华为·node.js·harmonyos
fellow994 小时前
把 DeepSeek Harness 搬进鸿蒙的 8 个坑
华为·harmonyos