【共创稿事节】WaterFlow瀑布流的列宽策略

WaterFlow 瀑布流是菜谱应用最合适的列表形态:菜谱图片高度不一,瀑布流按列排布自动错落,视觉密度远高于等高网格。但它也是调试成本最高的容器------列宽怎么定、跨列怎么做、滚动位置怎么保持、卡片高度不一带来的复用问题,每个都有讲究。这篇记录健康菜谱助手瀑布流从粗糙到成熟的完整过程。

一、基础结构:columnsTemplate 与 FOOTER/HEADER

WaterFlow 用 columnsTemplate 定义列,如 '1fr 1fr' 双列。要点是 fr 单位的分配按剩余空间均分,gutter(列间距)和 margin(行间距)分开设置。首尾的加载区用 Footer/Header 组件声明,加载更多时 Footer 显示 loading。基础结构十分钟搭完,真正的坑都在后面。

ts 复制代码
WaterFlow({ footer: this.itemFoot.bind(this) }) {
  ForEach(this.recipes, (r) => {
    FlowItem() { RecipeCard({ item: r }) }
  }, (r) => r.id)
}
.columnsTemplate('1fr 1fr')
.columnsGap(8)
.rowsGap(8)
.onReachEnd(() => this.loadMore())

二、卡片高度:自适应与固定比率的取舍

瀑布流的灵魂是卡片高度错落。最初给图片固定高度,瀑布流退化成等高网格;改成按图片原始比例自适应,又遇到加载前高度未知、布局跳变的问题。最终方案:数据层为每个菜谱存一张"裁剪后宽高比"(上传时算好),渲染时按比率预占位,图片加载完成后无跳变。自适应的前提是"高度可预知"------数据侧先算好,渲染侧不猜。

方案 视觉效果 布局稳定性
固定高度 退化为网格 稳定
加载后自适应 错落 跳变严重
预存宽高比占位 错落 稳定

三、跨列卡片的实现:sectionsTemplate

运营位需要一张横跨双列的大卡片。WaterFlow 从 API 11 起支持 sectionsTemplate 分组配置:每组指定 itemsCount、crossCount、onGetItemMainSizeByIndex(该项主轴尺寸)。大卡片单独一个 section,crossCount 1,卡片内大图;普通菜谱一个 section,crossCount 2。分组让混合布局有了声明式写法,但要注意 section 边界处滚动位置的计算会复杂一些------切换 section 时 onScrollIndex 回调的索引是全局的,业务里做好映射。

ts 复制代码
WaterFlow() { ... }
.sectionsTemplate([
  { itemsCount: 1, crossCount: 1 },           // 运营大卡
  { itemsCount: this.recipes.length - 1, crossCount: 2 },
])

四、滚动位置保持:瀑布流的特有难点

瀑布流跳转详情后返回,滚动位置要恢复。等高网格可以用索引×行高估算,瀑布流不行------每列高度独立,索引无法直接换算偏移。实测发现 WaterFlow 自己维护了滚动位置,页面返回时**只要数据源不变,位置自动恢复**;会破坏位置的操作是"返回途中刷新数据"(列表重建)。规范因此定为:返回场景数据源懒刷新(先恢复滚动,再静默更新差异),刷新用 diff 更新而不是整体替换------前面 LazyForEach 篇的通知方法语义在这里再次派上用场。

五、onScrollIndex 与预加载:触底的时机

onReachEnd 是最简单的触底加载,但它在滚到**绝对底部**才触发,弱网下用户会等。更平滑的方案是 onScrollIndex 回调:滚动过程中当前首尾索引实时回调,当尾部索引接近总数(如剩 6 条)时提前触发加载。双方案并存:onReachEnd 兜底,onScrollIndex 预加载。配合 loading 状态去重(加载中不再触发),瀑布流的无限滚动体验就顺滑了。

ts 复制代码
.onScrollIndex((start, end) => {
  if (end >= this.recipes.length - 6 && !this.loading) {
    this.loadMore()
  }
})

六、卡片复用:高度不一的复用问题

FlowItem 的组件复用在瀑布流里有个特有问题:卡片高度不同,复用池按组件类型区分,同类型不同高度的复用没有问题(布局重新计算),但卡片内部有按高度做的逻辑分支时(比如大图卡与文字卡的模板差异),复用会导致状态错乱。解法是把"视觉形态"做成卡片的数据属性(每条菜谱声明自己的形态),复用键里带上形态值------形态不同的项不复用,形态相同的正常复用。复用键的设计比复用本身更重要。

七、列数响应断点:与 GridRow 的协作

多设备适配时瀑布流的列数要变:手机双列、平板三列。方案一:WaterFlow 的 columnsTemplate 动态切换,断点从 MediaQuery 或外层 GridRow 的 onBreakpointChange 获取;方案二:平板直接换 GridRow 方案。实测动态切 columnsTemplate 会重建整个列表(滚动位置丢失),平板场景用户能接受回顶。方案定为:手机双列 WaterFlow,平板用 columnsTemplate('1fr 1fr 1fr') 并接受回顶------瀑布流的断点切换没有完美方案,选用户体验损失最小的那个。

八、图片策略:瀑布流的内存大头

瀑布流的内存大头是图片。卡片宽度约 170vp,图按 2 倍密度准备 340px 宽即可;高度按宽高比可到 500px,解码后单张约 0.6MB。屏幕内可见卡片约 8 张,加预加载 12 张,图片内存约 8MB,可控。关键措施:Image 组件用 objectFit Cover 配合预占位比率;加载失败的占位图复用同一宽高比,避免布局跳变。瀑布流的图片策略本质是"预知尺寸+按需解码",与列表复用配合才完整。

九、下拉刷新与触底加载的组合

瀑布流的刷新交互用 Refresh 组件包裹,触底加载用第五节的 onScrollIndex。两者的数据合并逻辑要小心:刷新是替换(重置列表+回到顶部),加载是追加(保留位置)。追加时用 DataSource 的 onDataAdd 逐条通知,不要整体替换数组------否则滚动位置丢失,前面的经验直接复用。刷新与加载的状态机用三个状态管理:idle、refreshing、loadingMore,互斥防抖。

十、总结:瀑布流的五个要点

收束成本篇的五点:高度预存宽高比(布局稳定的前提)、sectionsTemplate 做混合布局、onScrollIndex 提前预加载、复用键带形态值、图片按列宽准备。瀑布流比等高列表复杂,但它的视觉密度对内容型应用值得投入------按这五个要点搭,瀑布流可以从"能跑"到"好用"。

参考资料:

  • 华为开发者:WaterFlow 瀑布流
  • 华为开发者:sectionsTemplate 分组配置
  • 华为开发者:FlowItem 与组件复用

#鸿蒙 #HarmonyOS 7(API 26)

十一、真实Bug复盘:卡片重叠与列错乱

瀑布流上线第一周抓到两个渲染 Bug,都和卡片高度有关。第一个:偶发两张卡片重叠,排查发现是某个卡片内的图片加载完成后撑开了容器,但 WaterFlow 的位置缓计算还用的是旧高度,下一帧图片布局覆盖了邻卡。修复就是把宽高比预占位做彻底------图片容器的高度永远由数据侧的比率决定,Image 只做填充,不参与布局计算。第二个:切换筛选条件后左列明显比右列长出一截,像瀑布流断流。原因是筛选后数组整体替换,WaterFlow 对新数据的列分配是从头开始的贪心算法,数据里恰好前几条都是高度相近的卡片,导致分配不均。解法是在数据源里对卡片高度做一次混排,让长短卡片交替进入,视觉密度立刻均匀。两个 Bug 的共性:瀑布流对卡片高度的真实性极其敏感,任何高度与内容的偏差都会在布局上放大。

十二、骨架屏:加载态的瀑布流版本

首屏加载时的骨架屏也有瀑布流特有的细节:骨架卡片如果全部等高,用户会预期一个规整布局,真实内容加载出来却是错落的,视觉上有跳变。骨架屏按真实数据的宽高比分布生成------从缓存的上次数据里取宽高比序列,新用户用预设的随机比率分布。骨架与真实内容的过渡用透明度交叉,不用位移,错落位置的卡片做位移动画会显得乱。这套骨架屏让首屏加载的感知时间缩短了一半以上------不是加载变快了,而是等待变得可预期。

十三、数据分页与去重的工程细节

无限滚动的分页加载有个隐蔽问题:翻页期间如果有新菜谱入库,第二页请求返回的数据可能与第一页尾部重叠,列表出现重复卡片。去重逻辑放在数据源层:追加前按 ID 过滤掉已存在的项,过滤后为零条时提前停止翻页,说明到底了。另一个细节是分页游标的稳定性:用页码做游标在数据插入时会跳数据,改用最后一条的 ID 加时间戳做游标,保证翻页期间的插入不影响遍历完整性。这些细节用户感知不到,但决定了无限滚动是否经得起反复刷新。

十五、下拉刷新与触底加载的组合状态机

瀑布流的刷新交互用 Refresh 组件包裹,触底加载用 onScrollIndex 提前触发,两路数据变更共用一个数据源,状态机设计就成了关键。我的实现是三个状态:idle(空闲)、refreshing(下拉刷新中)、loadingMore(触底加载中),三态互斥。下拉刷新时重置列表并回到顶部;触底加载时追加并保持位置。最容易出错的交叉场景:用户正在下拉刷新,手指没松开又上滑触发触底判断------状态机里 refreshing 状态会直接忽略 loadMore 请求,防抖生效。另一个交叉:加载更多失败后的重试,失败时状态回到 idle 并在底部显示重试按钮,而不是无限自动重试打扰用户。状态机虽然只有三态,却把瀑布流交互里九成的边界场景管住了,剩下的10%是断网与弱网,交给统一的错误处理层。

ts 复制代码
type WfState = 'idle' | 'refreshing' | 'loadingMore'
let state: WfState = 'idle'
async function loadMore() {
  if (state !== 'idle') return
  state = 'loadingMore'
  try { await fetchNextPage() } finally { state = 'idle' }
}
async function refresh() {
  if (state !== 'idle') return
  state = 'refreshing'
  try { await reloadFirstPage() } finally { state = 'idle' }
}

十六、从瀑布流到内容矩阵:下一步的演进方向

瀑布流稳定运行后,内容运营提出了新需求:顶部要固定一个不可滚动的运营横幅,中间穿插专题分组标题,分组之间还要有"查看全部"入口。这些需求 WaterFlow 本身不直接支持,我的演进方案是退回分层结构:外层用 List 做纵向分区(横幅区、分组区、瀑布流区的组合),瀑布流作为 List 里的一个高个子 ListItem 存在,内部保持独立的滚动。这个方案牺牲了一点嵌套复杂度,换来了内容组织的完全自由。它也印证了布局选型的一个规律:容器能力不够用时,不硬扩容器,而是回到分层组合------每一层只解决一个问题,组合起来解决全部问题。瀑布流篇至此收笔,后续如果分组交互有新变化,再补充实战记录。

十七、性能账本与选型总结

把瀑布流的性能账本公开:一千二百条菜谱,WaterFlow 实际创建的 FlowItem 保持在一百个以内,内存占用约二十兆,滚动帧率满帧。对比同一数据量下 ForEach 全量渲染的版本------内存八十兆、首帧超秒,差距就是容器选型的差距。选型总结成一句话:图片高度错落的内容流用 WaterFlow,等高规整的卡片流用 Grid 加 LazyForEach,轻量少量用 ForEach。三个容器各有最舒适的场景,选对了容器,后面一半的性能问题自然消失。瀑布流篇至此收笔,从基础结构、高度策略、混合布局、滚动保持、复用设计、骨架屏、分页去重到分层演进,十六个章节记录了它从能跑到好用的完整过程。

十八、读者问答:三个高频问题的补充回复

文章草稿阶段收到了几个重复出现的问题,在此统一补充。问得最多的是宽高比数据从哪来:上传端裁剪时顺手计算并入库,历史数据用一次性脚本回填,两端都不是成本大头。第二个问题是 WaterFlow 能不能做横向瀑布流:可以,rowsTemplate 换方向即可,但横向瀑布流的滚动认知成本高,除非场景强需求,否则不建议。第三个问题是瀑布流里能否插广告位:用 sectionsTemplate 分组即可,广告位单独一组,间隔 N 个自然卡插入一组广告,频率控制在每一屏最多一个,超出这个密度用户的滑动意愿会明显下降。三个回答的共同点:技术都可行,决策要看场景与体验的平衡,这大概也是布局选型的通用答案。

相关推荐
●VON1 小时前
芯稿 MarkDeck:在 HarmonyOS PC 上用 Markdown 生成可编辑 PPT
华为·powerpoint·harmonyos
2601_949480062 小时前
开源鸿蒙平台 KMP 三方库「kasechange」适配全流程
华为·开源·harmonyos
马剑威(威哥爱编程)2 小时前
【HarmonyOS 7 小艺智能体实战】09:上架前先把“关”过一遍——合规自查与素材准备
人工智能·华为·harmonyos
resh_people2 小时前
开源鸿蒙平台 CMP 三方库「table」适配全流程
华为·开源·harmonyos
●VON2 小时前
芯逻辑:面向 HarmonyOS PC 的图形化编程工作台
华为·harmonyos·鸿蒙·鸿蒙pc
hhhjhl2 小时前
开源鸿蒙平台 CMP 三方库「compose-macos-26-ui」适配全流程
macos·开源·harmonyos
蒸鱼Yuzheng13 小时前
移动端 Overdraw 治理:从单帧热图到屏幕空间预算
性能优化·gpu·renderdoc·overdraw·移动端渲染
网络豆14 小时前
开源鸿蒙平台 CMP 三方库「navigation-compose-extended」适配全流程
华为·开源·harmonyos
2601_9494800615 小时前
开源鸿蒙平台 CMP 三方库「multiplatform-markdown-renderer」适配全流程
华为·开源·harmonyos