HarmonyOS 7 InsightBoard 多形态适配实录 03:ArkUI × FoldSplitContainer:悬停态折痕避让与上下区域内容重组【鸿蒙心迹】
前两期把 InsightBoard 的两条基础边界定了下来:窗口宽度决定布局,折叠状态只描述设备形态。
01 用 windowSizeChange 把 COMPACT / MEDIUM / EXPANDED 三档断点跑通;02 再把 foldStatusChange 和窗口事件拆开,并通过 80ms 的状态协调窗口避免一次开合动作触发两次页面重排。
做到这里,普通“展开 / 折叠”已经比较稳定。真正把设备停在半折叠角度以后,新的问题才出现:页面虽然没丢状态,但内容会压到折痕附近,操作区域也并不自然。
这一轮没有再改断点,而是开始使用 HarmonyOS 7 / API 26 新增的 FoldSplitContainer 思路,专门处理悬停态和折痕区域。
本轮固定数据:
taskId: adapt_20261002_03
foldStatus: HALF_FOLDED
isHoverMode: true
appRotation: 0
windowStatus: FULL_SCREEN
primaryRegion: 392vp
hingeSafeGap: 36vp
secondaryRegion: 487vp
primary: 今日概览 + 趋势图
secondary: 任务列表 + 操作区
extra: 关闭
selectedItem: insight_042
scrollOffset: 1280vp
transitionCost: 38ms
status: HOVER_READY

一、半折叠不是“把屏幕高度除以二”
最开始我做过一个很粗暴的版本。检测到设备进入半折叠后,上半屏 50%,下半屏 50%。
看起来像是完成了悬停态,实际体验很差。折痕本身不是普通内容区域,标题、按钮、列表项压在折痕附近时,视觉上会被截断,触控也不舒服。另一方面,上下区域的任务权重不同。InsightBoard 上半屏更适合保留概览、图表和结果,下半屏更适合放列表、筛选和操作控件。机械对半切无法表达这种优先级。
另外,半折叠状态下的可用区域还会受到窗口状态、旋转方向影响。不能只拿一个“折叠角度”就直接决定全部布局。
HarmonyOS 7 / API 26 的 ArkUI 变更中新增了 FoldSplitContainer,提供 primary / secondary / extra 三个区域,以及展开态、悬停态和折叠态各自的布局参数;HoverModeStatus 里还能拿到 foldStatus、isHoverMode、appRotation 和 windowStatusType。这正好比我自己在页面里硬算上下高度更合适。
二、先让 FoldSplitContainer 只负责“区域”,不要再接管业务状态
这一篇新建:
components/
├── HoverOverviewPane.ets
├── HoverTaskPane.ets
└── HoverFoldShell.ets
managers/
└── HoverLayoutCoordinator.ets
FoldSplitContainer 只负责区域关系。
当前示意代码解决的是“把折叠状态和业务内容继续耦合”的问题:
import {
FoldSplitContainer,
PresetSplitRatio
} from '@ohos.arkui.advanced.FoldSplitContainer'
@Component
export struct HoverFoldShell {
@Prop selectedItemId: string = 'insight_042'
@Builder
primaryRegion() {
HoverOverviewPane({
taskId: 'adapt_20261002_03'
})
}
@Builder
secondaryRegion() {
HoverTaskPane({
selectedItemId: this.selectedItemId
})
}
build() {
FoldSplitContainer({
primary: () => {
this.primaryRegion()
},
secondary: () => {
this.secondaryRegion()
},
expandedLayoutOptions: {
horizontalSplitRatio:
PresetSplitRatio.LAYOUT_2V3
},
hoverModeLayoutOptions: {
horizontalSplitRatio:
PresetSplitRatio.LAYOUT_1V1,
showExtraRegion: false
},
foldedLayoutOptions: {
verticalSplitRatio:
PresetSplitRatio.LAYOUT_1V1
}
})
}
}
这里最重要的不是 LAYOUT_1V1。而是 primary / secondary 里面传入的仍然是业务组件。选中的 insight_042 继续来自 Store,不因为容器从展开态切到 HoverMode 就重新创建业务数据。
如果后面退出悬停态,容器可以重排区域,但业务 ID 仍然存在。
三、折痕安全间距要成为布局参数,而不是 UI 里临时塞 Margin
真正跑真机以后,我发现折痕附近最难处理的不是“区域高度”,而是安全间距。
第一版我在上区域底部写 .margin({ bottom: 18 }),下区域顶部再写 .margin({ top: 18 })。单个页面能解决,换一个内容页又要重新写。
所以现在 HoverLayoutCoordinator 统一生成:
primaryRegionHeight
hingeSafeGap
secondaryRegionHeight
本轮最终是:
392vp
36vp
487vp
这一段代码解决的是“安全距离散落在多个页面”的问题:
export interface HoverRegionSnapshot {
taskId: string
primaryVp: number
hingeGapVp: number
secondaryVp: number
hoverReady: boolean
}
export class HoverLayoutCoordinator {
private readonly hingeGapVp: number = 36
resolve(
totalHeightVp: number
): HoverRegionSnapshot {
const usable =
totalHeightVp - this.hingeGapVp
const primary =
Math.floor(usable * 0.446)
const secondary =
usable - primary
return {
taskId: 'adapt_20261002_03',
primaryVp: primary,
hingeGapVp: this.hingeGapVp,
secondaryVp: secondary,
hoverReady: true
}
}
}
这里的 36vp 和比例都是 InsightBoard 当前内容下的工程值,不是系统强制参数。官方 API 提供容器和分区能力,具体哪一块内容需要多少空间,仍然要根据自己的产品信息密度做验证。
四、悬停态里,上半屏和下半屏不应该做同一种事
InsightBoard 的 HoverMode 最后没有把原来的 MEDIUM 双栏上下旋转 90 度。
我重新看了一遍用户动作。普通展开态更接近“看列表 → 点内容 → 看详情”,悬停态更接近“上屏持续看状态,下屏持续操作”。
因此最终拆成两层。
1. Primary
放今日任务、趋势图、当前洞察摘要、任务进度。这块更像观察区。
2. Secondary
放任务列表、筛选、继续 / 暂停、详情入口。这块更像交互区。
以前 insight_042 的详情大图在右侧;进入悬停态后,我没有把它塞进上屏,而是保留一张简化摘要卡,完整详情仍然通过下屏入口打开。
实际用下来,这比“尽量把原页面内容全部保留”更自然。多形态适配真正增加的应该是场景效率,不是把同一张页面切成两块。
五、onHoverStatusChange 只更新姿态快照
FoldSplitContainer 提供 onHoverStatusChange,回调状态包括是否进入悬停模式、折叠状态、应用旋转和窗口状态。
我没有在这个回调里做网络请求、重新拉列表或者清空 Navigation。
代码只更新姿态:
private onHoverStatusChange = (
status: HoverModeStatus
): void => {
this.hoverSnapshot = {
taskId: 'adapt_20261002_03',
isHoverMode: status.isHoverMode,
foldStatus: status.foldStatus,
appRotation: status.appRotation,
windowStatusType:
status.windowStatusType
}
this.scheduleLayoutApply()
}
它和 02 的原则完全一致:系统事件先更新系统状态,Coordinator 统一计算,UI 只消费 Snapshot。以后窗口退出全屏、进入自由窗口时,悬停姿态虽然还在,但当前窗口可能已经不适合使用上下分区,Coordinator 可以根据 windowStatusType 决定是否退回普通 MEDIUM。
六、这次我专门测了“悬停态打开以后再转屏”
第一轮测试只覆盖 EXPANDED → HALF_FOLDED,没有问题。
把设备在悬停态里改变方向以后,页面第一次出现了内容抖动。
原因是 appRotation 发生变化后,旧的 region snapshot 还在使用,上下区域会短暂拿到上一方向的尺寸。
最后我把 Apply 条件收紧:
isHoverMode
+
appRotation
+
windowStatusType
+
viewport snapshot
四个值都稳定以后才应用一次。
这和前一篇的 80ms 合并思路类似,但不是简单复制 Timer。03 更关注“状态是否完整”。如果少一个字段,就先不重排。
七、DevEco 图里,折痕区域必须能从日志解释出来
03 的 DevEco 图不再看 412 / 824 两个普通断点,而是固定看:
taskId=adapt_20261002_03
isHoverMode=true
foldStatus=HALF_FOLDED
primary=392vp
hingeGap=36vp
secondary=487vp
status=HOVER_READY

当前 HiLog:
hoverStatus isHoverMode=true
foldStatus=HALF_FOLDED
appRotation=0
windowStatus=FULL_SCREEN
resolveRegion primary=392vp hingeGap=36vp secondary=487vp
selectedItem preserved insight_042
scrollOffset preserved 1280vp
transitionCost=38ms
status=HOVER_READY
如果页面上屏高度正常,但详情跳回第一条,说明不是 FoldSplitContainer 的问题,而是 Store 恢复出了问题。如果业务状态正常,但按钮贴着折痕,才回头查 region snapshot。
八、最终运行结果不是“分成上下两块”,而是上下两块各自有明确职责
最终运行图:

本轮统一状态:
taskId: adapt_20261002_03
device: Foldable
foldStatus: HALF_FOLDED
isHoverMode: true
windowStatus: FULL_SCREEN
primary: 392vp
hingeSafeGap: 36vp
secondary: 487vp
primaryRole: OVERVIEW
secondaryRole: ACTION
selectedItem: insight_042
scrollOffset: 1280vp
transitionCost: 38ms
status: HOVER_READY
这一篇做完以后,InsightBoard 的折叠屏适配终于不只是“展开 / 折叠”。悬停态开始变成真正不同的交互场景。
下一篇会从折叠屏离开,继续同一个 InsightBoard,但是把应用放到平板、PC / 2in1 和自由窗口里。重点不是再加一个断点,而是回答一个更实际的问题:窗口已经足够大以后,到底应该增加什么信息,哪些导航应该常驻,哪些内容应该继续保持克制。
也就是说,04 会处理“大屏信息密度”,而不是简单把 EXPANDED 拉得更宽。
参考资料
- HarmonyOS 多设备通用适配指南:https://developer.huawei.com/consumer/cn/multidevice/adaptive-apps/
- ArkUI API 26 FoldSplitContainer 变更:https://developer.huawei.com/consumer/cn/doc/doccenter-release-notes/js-apidiff-arkui-b031
更多推荐



所有评论(0)