HarmonyOS ArkTS 创建瀑布流 WaterFlow:从“能跑起来”到“真正在项目里好用”的写法
WaterFlow 适合那种 列宽相等、但每个卡片高度不一样 的场景:商品流、图片墙、内容推荐流……你要的不是“整齐”,而是“错落有致、空间利用率高”。官方文档明确 WaterFlow 用于构建瀑布流布局,并支持条件渲染、循环渲染、懒加载等方式生成子组件。developer.huawei.com+1
1)WaterFlow 的“脑内模型”先对齐
WaterFlow = 多列容器(列宽相等) + 高度不等的卡片(自动往最短列里落)。
你写的时候,只需要抓住三点:
- WaterFlow 是容器
- FlowItem 是每个卡片/单元
- 列数、间距、滚动事件 是你在项目里最常调的东西developer.huawei.com+1
2)最小可运行示例:两列瀑布流(先把骨架搭起来)
这段代码的目标很简单:两列、间距有、卡片高度不一样、能滚动。
@Entry
@Component
struct WaterFlowBasicDemo {
@State private items: number[] = Array.from({ length: 40 }, (_, i) => i + 1)
build() {
WaterFlow() {
ForEach(this.items, (n: number) => {
FlowItem() {
// 卡片内容:高度故意做成不一致,才能看到“瀑布”效果
Column({ space: 8 }) {
// 模拟封面
Rect()
.width('100%')
.height(60 + (n % 5) * 18) // 高度变化
.fill(0xFFEAF2FF)
.radius(12)
Text(`第 ${n} 个卡片`)
.fontSize(14)
.fontWeight(FontWeight.Medium)
Text('这里放副标题/价格/标签都行')
.fontSize(12)
.fontColor(0xFF888888)
}
.padding(12)
.backgroundColor(0xFFFFFFFF)
.borderRadius(12)
}
}, (n: number) => `${n}`)
}
.columnsTemplate('1fr 1fr') // 两列
.columnsGap(10)
.rowsGap(10)
.padding(12)
.backgroundColor(0xFFF5F6F8)
.width('100%')
.height('100%')
}
}
columnsTemplate / columnsGap / rowsGap这套写法在社区实践里也非常统一:WaterFlow + FlowItem,再配列模板与间距,基本就成型了。bbs.itying.com+1
3)项目里最常见的需求:上拉加载更多(别等用户翻到底才尴尬)
你做内容流,迟早会遇到“滚到底就继续请求下一页”。
WaterFlow 的 API 参考里提供了不少与滚动/布局相关的能力(比如分组混合列数布局等),实际项目里你最先用上的通常是“到达末尾触发加载”。developer.huawei.com
下面给你一个实用写法:用一个状态控制 isLoading,并在触发时 append 数据。
@Entry
@Component
struct WaterFlowLoadMoreDemo {
@State private items: number[] = Array.from({ length: 30 }, (_, i) => i + 1)
@State private isLoading: boolean = false
private async loadMore() {
if (this.isLoading) return
this.isLoading = true
// 模拟网络延迟
await new Promise<void>((r) => setTimeout(() => r(), 700))
const start = this.items.length + 1
const more = Array.from({ length: 20 }, (_, i) => start + i)
this.items = [...this.items, ...more]
this.isLoading = false
}
build() {
Column() {
// 顶部筛选栏(真实项目很常见)
Row({ space: 10 }) {
Text('推荐').fontSize(16).fontWeight(FontWeight.Bold)
Text(this.isLoading ? '加载中…' : '正常').fontSize(12).fontColor(0xFF888888)
Blank()
}
.padding(12)
.backgroundColor(0xFFFFFFFF)
WaterFlow() {
ForEach(this.items, (n: number) => {
FlowItem() {
Column({ space: 6 }) {
Rect().width('100%').height(70 + (n % 6) * 14).fill(0xFFEAF2FF).radius(12)
Text(`卡片 #${n}`).fontSize(14)
}
.padding(12)
.backgroundColor(0xFFFFFFFF)
.borderRadius(12)
}
}, (n: number) => `${n}`)
// 尾部 loading 占位(不做也行,但做了更像“真 App”)
FlowItem() {
Row() {
Text(this.isLoading ? '正在加载更多…' : '滑到底会自动加载')
.fontSize(12)
.fontColor(0xFF999999)
Blank()
}
.padding(12)
}
}
.columnsTemplate('1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.padding({ left: 12, right: 12, bottom: 12 })
.backgroundColor(0xFFF5F6F8)
.width('100%')
.height('100%')
// 这里不同版本 WaterFlow 的末尾触发事件/命名可能有差异:
// 你如果发现没有 onReachEnd,就去 API 参考页按“reach / end / scroll”关键词搜一下对应事件。
.onReachEnd(() => this.loadMore())
}
.width('100%')
.height('100%')
}
}
我特意把“筛选栏 + 流式内容 + 尾部提示”放进来,因为这就是你做推荐流/商品流时最像项目的一套结构。WaterFlow 在“列表/网格概述”里也被定位为这种错列布局的典型选择。developer.huawei.com+1
4)别忽略这一点:WaterFlow 不是 Grid,它解决的是“高度不一致”
很多人第一次写瀑布流会纠结:
“我用 Grid 也能两列啊,为啥还要 WaterFlow?”
关键差异在于:
- Grid 更像“表格”:行列规整,卡片高度最好差不多
- WaterFlow 是“错列排布”:卡片高度不等也能自然填满空隙developer.huawei.com+1
你做商品图(高矮不同)、图文卡(内容长短不同),WaterFlow 才是那个“不别扭”的选择。
5)进阶:同一个瀑布流里“不同分组用不同列数”(你真写电商会用到)
比如:
- 顶部“活动入口”用 4 列小图标
- 下面商品流用 2 列卡片
- 再下面“猜你喜欢”用 3 列小卡片
WaterFlow 的 API 参考里提到:可以通过 FlowItem 分组实现“同一个瀑布流内部各分组使用不同列数的混合布局”,并且这种模式会忽略 columnsTemplate/rowsTemplate。developer.huawei.com
这块我建议你等你真的要做“一个页面混多种网格密度”时再上,不然一开始就写混合分组,调试成本会明显增加。
6)我写 WaterFlow 时的“稳妥习惯”(少踩坑)
- 给 ForEach 一个稳定 key:刷新/分页时不容易整片抖动
- 卡片容器 width(‘100%’) + padding:不然视觉会忽大忽小
- 间距一定要显式设置:
columnsGap/rowsGap不写默认会挤 - 先用 ForEach 跑通,再换 LazyForEach:数据量大了再优化更顺(WaterFlow 官方也强调支持懒加载思路)developer.huawei.com+1
7)你要是想把它变成“真正的项目模板”,我下一步可以直接给你
给你做一个完整的小项目页(和你之前要的“实战示例项目”同风格):
- 顶部:筛选/排序(推荐 / 销量 / 价格)
- 中间:WaterFlow 卡片(图片、标题、标签、价格)
- 底部:触底自动分页 + 骨架屏占位
- 点击卡片:进详情页(用 Navigation push)
1万+

被折叠的 条评论
为什么被折叠?



