CPF-Flutter 社区提出折叠场景分栏(平行视界) 方案

最近 CPF-Flutter 针对折叠屏场景提了平行视界的适配方案,这个方案从特性从 Flutter 3.35.8-ohos-1.0.3 版本就开始支持了,方案除了分栏,主要也是为了现在阔折叠场景会有旋转和统一路由做支持:

屏幕状态 布局表现
横屏/宽屏 左右两侧同时显示,左侧主页 + 右侧详情页
竖屏/窄屏 单栏显示,智能切换主页或详情页
折叠屏展开 自动切换为分栏布局
折叠屏折叠 自动切换为单栏布局

主要是提供了自动适应屏幕尺寸变化的能力,在支持分栏目的同时,一套代码适配竖屏场景,然后兼容默认的多种路由方案,官方提供的 gitcode.com/CPF-Flutter... 就实现了 8 种不同的路有 Demo 。

目前分栏功能支持范围有:

  • 支持基于 Navigator 的路由方式和基于 Router 的声明式路由方式,包括 RouterDelegate、RouterConfig、go_router库等
  • Router 模式基于单 NavigatorState + 双 Overlay 实现,在 Navigator 底层统一处理,对上层透明

分栏功能可以通过放置 ohos/entry/src/main/resources/rawfile/split_config.json ,同时代码配置enableWideWindowSplit 或者 enableSquareWindowSplittrue 启用,配置里还可以指定主页路由名、全屏页面列表等:

json 复制代码
{
  "splitOptions": {
    "enableWideWindowSplit": false,
    "enableSquareWindowSplit": true,
    "homePage": "/",
    "fullScreenPages": ["/videoPlayer", "/imageViewer"],
    "enableReducedContainerSize": true,
    "supportLandscapeFullscreen": true
  }
}
配置项 类型 默认值 说明
enableWideWindowSplit bool false 宽屏设备分栏开关
enableSquareWindowSplit bool false 方屏设备分栏开关
homePage String? null 主页路由名,null 时自动识别主页
fullScreenPages List \[\] 强制全屏显示的页面路由名列表
enableReducedContainerSize bool true 组件宽度基于屏幕一半显示
supportLandscapeFullscreen bool true 横屏时是否全屏显示

默认情况下,enableWideWindowSplit 是宽窗口分栏开关,分栏的触发条件是:

  • 屏幕宽度 > 600dp 和高度 > 600dp
  • 屏幕宽高比 > 1.2
  • 横屏状态

enableSquareWindowSplit 是方形屏幕分栏开关,分栏的触发条件是:

  • 屏幕宽度 > 600dp 和高度 > 600dp
  • 屏幕宽高比、高宽比均小于1.2

也就是,默认情况下,Navigator 内部真正掌握导航语义的是 _history,这部分的 push、pop、replacement、RouteObserver、页面动画、Route 生命周期等,围绕的都是这条历史去处理,然后 Route 到了显示阶段,就会产生一组 OverlayEntry,最后交给 Overlay 排列和绘制,而 OHOS 分栏方案就是抓住了这一层:

它继续保留 「一个 NavigatorState + 一条 _history」这种情况,只是通过不同 Overlay 来实现分栏,路由历史还是通过 NavigatorState 统一维护。

也就是这样,可以做到无缝兼容普通屏幕场景,同时不需要做什么大幅修改兼容。

之所以不用多个 Navigator ,也是因为现在 Flutter 的第三方路由支持都有自己的一套实现,比如 go_router 还会建立自己的 Navigator wrapper ,这种情况下多个 Navigator 的方案很难做到拦截和保证真正负责当前页面的 Navigator

然后 Flutter Navigator 内部在 push、pop、remove 等操作之后,要统一刷新 Route 生命周期,这时候内部的 _flushHistoryUpdates() 会遍历 _history,推进 _RouteLifecycle 状态,比如 adding、pushing、popping、removing、dispose,然后同时完成 observer 通知、Route announcement、待销毁 Route 的释放,最后重新整理 Overlay 中所有 Route 对应的 entries,执行 :

scss 复制代码
overlay.rearrange(_allRouteOverlayEntries)

也就是说,前面 Navigator 已经决定好了哪些 Route 活着、谁在 top、谁该 dispose,最后把所有活跃 Route 的 OverlayEntry 按正确层级重新排进一个 Overlay。

这种情况下,分栏方案保留前面的生命周期状态机,只在这个流程周围插入分栏所需的状态处理,最终把单 Overlay 的 rearrange 换成 rearrangeSplitOverlays()

所以简单来说就是,Navigator 继续决定「有哪些页面、页面之间是什么关系」,SplitViewNavigatorPolicy 决定「这些页面的 OverlayEntry 最后放左边还是右边」。

所以比如主页在这里成了整条 _history 的分界点,比如主页出现以前是 Splash - Login ,所有页面都在左边的 Overlay 中运行,右边维持占位页,等 SDK 检测到真正的 Home Route 后进入第二阶段,Home 留在左侧,之后 push 出来的详情 Route 进入右 Overlay,实际屏幕上只看到各 Overlay 当前可见的顶层内容:

当然,双 Overlay 带来的一个直接问题是 Modal Barrier ,普通 Flutter 只有一个 Overlay,showDialog() 之类的对应的 PopupRoute 会把半透明 ModalBarrier 和 Dialog 本身插入同一个 Overlay,所以 barrier 天然能够覆盖整个窗口。

现在屏幕被拆成 Left Overlay | Right Overlay 之后,假如详情页位于右侧,然后在详情页弹出 Dialog,PopupRoute 的 barrier 也属于右 Overlay,它最多只能盖住右半屏,左侧 Home 依旧可以点击,这在交互语义上kennel会有点奇怪。

所以这里还专门 设计了 MirrorBarrier ,右侧出现弹窗时,在左 Overlay 中再创建一个镜像 barrier,弹窗在左侧时,也可以对另一侧做对应遮挡:

镜像层复用原 PopupRoute 的 barrierColor 和动画,因此左右两半的透明度变化保持同步,它还处理了焦点保存与恢复,以及多个连续 PopupRoute 的生命周期,避免 Dialog 已经消失而另一半蒙层还残留。

从实现兼容看,这确实是一套挺不错的分栏方案,最主要它能兼容已有的路由实现,另外 CPF-Flutter 版本也支持到了 3.44 ,特别感觉苹果出现 Duo 之后,各大 App 应该也会开始"被动"适配折叠屏,所以这个也是可以考虑的一个方向。

相关推荐
aixingpan1 小时前
aixingpan.cn API开发文档:api_docs_trichart_natal_marx_transit2接口指南
前端·php
csj501 小时前
前端基础之《React(13)—表单绑定、列表渲染》
前端·react.js
csj501 小时前
前端基础之《React(12)—条件渲染》
前端·react.js
IT_陈寒1 小时前
Java序列化坑了我三天,原来问题出在这个默认方法
前端·人工智能·后端
事圆则缓1 小时前
Java 常用 API 如何建模 Android 订单业务
android
雪芽蓝域zzs1 小时前
第四十八节:驾驶舱大屏进阶:数字滚动动画 + 渐变边框美化
前端·javascript·vue.js
Railshiqian2 小时前
android16单手模式代码走读
android·多屏·单手模式
ly76892 小时前
Spring Boot 3.2 虚拟线程在 Web 层与 JDBC 池的实践边界
前端·spring boot·后端·tomcat·jdbc·虚拟线程
Bingo_BIG2 小时前
LOV列表的查询
前端·javascript·lov