LazyLayoutAlgorithm 一用就全量创建?HarmonyOS 7 可视区懒加载最容易写错的两个参数
列表改成 LazyDynamicLayout 后,首屏仍然创建了几百个卡片,启动时间和内存几乎没变。问题往往不在数据源,而在自定义算法里调用了会展开全部节点的取子节点方式。API 26 的 LazyCustomLayoutAlgorithm 明确要求:onMeasure 和 onLayout 中按需获取节点时使用 LAZY_NOT_EXPAND,统计总数时使用 ALL_NOT_EXPAND;否则“懒加载容器”会被自己的算法提前展开。
先把边界说明白
| 关注点 | 应该怎么理解 | 容易踩的坑 |
| getChild | 传入 LAZY_NOT_EXPAND | 只为当前需要的索引请求节点 |
| getChildrenCount | 传入 ALL_NOT_EXPAND | 统计数量时不触发全量创建 |
| helper | 可能为 undefined | 当前组合不支持懒加载时必须有退化路径 |
这张表不是把官方文档换一种说法,而是把接口边界变成能检查的工程条件。适配前先确认当前应用是否命中条件,再决定是否改代码。没有命中的模块不应为了“统一写法”一起重构,命中的模块也不能只改到编译不报错。
案例一:首屏只创建可视窗口附近的节点
先让算法只计算可视区索引,再取这些节点。下面的纯函数不依赖 UI 框架,负责把滚动偏移换成“起始索引、结束索引”,并额外保留一屏缓冲,避免慢速滚动时频繁创建。真正的 onMeasure 再使用这个结果逐个调用 getChild(index, ExpandMode.LAZY_NOT_EXPAND)。
type Range = { start: number; end: number }; function visibleRange(offset: number, viewport: number, item: number, count: number): Range { const safeItem = Math.max(1, item); const first = Math.floor(Math.max(0, offset) / safeItem); const visible = Math.ceil(Math.max(0, viewport) / safeItem); return { start: Math.max(0, first - visible), end: Math.min(count - 1, first + visible * 2) }; } const range = visibleRange(480, 640, 160, 1000); if (range.start !== 0 || range.end !== 11) throw new Error('可视区计算错误');这一段先验证外围决策。它的价值是让输入和结果可重复,不依赖页面当前碰巧处于什么状态;接入 ArkUI 或系统能力时,再把结果映射成实际节点、路由或接口调用。
案例二:helper 不可用时不要假装仍在懒加载
官方说明列出了 helper 可能为 undefined 的组合,例如部分 WaterFlow 多列/分段模式,以及 List 开启 lanes、chainAnimation 或 scrollSnapAlign 的情况。遇到这种状态,算法应记录一次能力降级并使用受控的普通布局,而不是继续读取可视区字段导致异常,也不能悄悄把全量展开当作性能优化成功。
type LazyMode = 'lazy' | 'bounded-fallback'; function resolveLazyMode(helperAvailable: boolean, count: number): { mode: LazyMode; limit: number } { if (helperAvailable) return { mode: 'lazy', limit: count }; return { mode: 'bounded-fallback', limit: Math.min(count, 60) }; } const fallback = resolveLazyMode(false, 500); if (fallback.mode !== 'bounded-fallback' || fallback.limit !== 60) throw new Error('退化策略失效');第二个案例故意覆盖与第一个不同的失败条件。真实工程还应加入快速重复操作、前后台切换、窗口尺寸变化、空数据和恢复路径,避免只验证一次成功流程。
为什么选择这种做法
直接换成 LazyDynamicLayout 只解决了容器入口,真正的性能收益取决于算法是否保持按需取节点。推荐把可视区计算做成纯函数,把 ArkUI 节点测量留在算法类中;两者分开后,滚动边界可以在宿主环境验证,节点生命周期再由 API 26 设备确认。
如果团队准备封装,建议把公开接口保持在“输入事实、输出决策”的层级,不让调用方直接依赖底层节点对象。这样既方便复用,也便于写断言;需要系统能力的部分留在薄薄的适配层,升级时更容易定位。
怎样验证,哪些结论还不能提前说
先把验证分成三层。第一层是纯逻辑:输入、状态转换和边界条件可以在宿主环境运行断言;第二层是 API 26 编译:检查接口签名、系统能力和模型约束;第三层才是目标设备:检查触摸、键鼠、屏幕朗读、横竖屏、窗口缩放和前后台恢复。三层证据不能混在一句“已经跑通”里。
当前示例中的纯 TypeScript 决策函数可以独立测试,用于证明分支没有自相矛盾;涉及 ArkUI 节点、系统手势、跨设备能力和系统服务的片段,仍要使用 API 26 SDK 编译,并在支持该能力的 HarmonyOS 7 设备或云调试设备上验收。这样写不是保守,而是避免把没有发生过的真机结果当成事实。
动态页面还要观察状态变化后的第二次结果:首次进入正确,不代表弹窗关闭、列表更新或窗口缩放后仍然正确。每次状态切换都应重新核对当前节点、焦点目标、返回路径和数据快照,避免旧缓存继续影响新页面。
建议每次留下以下记录:
- DevEco Studio、SDK、targetSdkVersion 和设备系统版本。
- 触发输入、页面层级、窗口尺寸、操作方式与最终状态。
- 正常路径、空数据、快速重复操作、旋转或窗口缩放后的结果。
- 屏幕朗读、字体放大、深浅色和键盘焦点是否仍然可用。
- 性能对照使用同一批数据、同一设备状态和同一测量区间。
可以复用成什么
不要把适配判断散落在页面 Builder 中。更稳的结构是“能力探测或尺寸输入 -> 纯函数决策 -> 页面渲染 -> 设备证据”。纯函数负责输出稳定的布局或交互意图,页面只消费结果;下一次系统升级时,优先修改边界层和测试样本,不必把所有页面重新翻一遍。
发布前自查
- 文章讨论的是 HarmonyOS 7 / API 26 当前资料,不拿旧版本页面替代新接口。
- 两个案例的输入、失败现象和解决目标不同,不是同一段代码换名称。
- 代码中的常量有来源;没有来源的数值明确写成示例策略,不伪装成系统规定。
- 性能、兼容性与无障碍结论都有可重复的检查方法。
- 日志不保存账号、文件内容、设备标识和其他敏感数据。
官方资料
- LazyLayoutAlgorithm API 参考
- 2026 年 7 月开发者月刊