HarmonyOS ArkTS 创建瀑布流 WaterFlow:从“能跑起来”到“真正在项目里好用”的写法

HarmonyOS ArkTS 创建瀑布流 WaterFlow:从"能跑起来"到"真正在项目里好用"的写法

鸿蒙第四期开发者活动

WaterFlow 适合那种 列宽相等、但每个卡片高度不一样 的场景:商品流、图片墙、内容推荐流......你要的不是"整齐",而是"错落有致、空间利用率高"。官方文档明确 WaterFlow 用于构建瀑布流布局,并支持条件渲染、循环渲染、懒加载等方式生成子组件。developer.huawei.com+1


1)WaterFlow 的"脑内模型"先对齐

WaterFlow = 多列容器 (列宽相等) + 高度不等的卡片(自动往最短列里落)。

你写的时候,只需要抓住三点:

  1. WaterFlow 是容器
  2. FlowItem 是每个卡片/单元
  3. 列数、间距、滚动事件 是你在项目里最常调的东西developer.huawei.com+1

2)最小可运行示例:两列瀑布流(先把骨架搭起来)

这段代码的目标很简单:两列、间距有、卡片高度不一样、能滚动。

scss 复制代码
 @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 数据。

scss 复制代码
 @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/rowsTemplatedeveloper.huawei.com

这块我建议你等你真的要做"一个页面混多种网格密度"时再上,不然一开始就写混合分组,调试成本会明显增加。


6)我写 WaterFlow 时的"稳妥习惯"(少踩坑)

  • 给 ForEach 一个稳定 key:刷新/分页时不容易整片抖动
  • 卡片容器 width('100%') + padding:不然视觉会忽大忽小
  • 间距一定要显式设置columnsGap/rowsGap 不写默认会挤
  • 先用 ForEach 跑通,再换 LazyForEach :数据量大了再优化更顺(WaterFlow 官方也强调支持懒加载思路)developer.huawei.com+1

7)你要是想把它变成"真正的项目模板",我下一步可以直接给你

给你做一个完整的小项目页(和你之前要的"实战示例项目"同风格):

  • 顶部:筛选/排序(推荐 / 销量 / 价格)
  • 中间:WaterFlow 卡片(图片、标题、标签、价格)
  • 底部:触底自动分页 + 骨架屏占位
  • 点击卡片:进详情页(用 Navigation push)
相关推荐
_膨胀的大雄_2 小时前
01-创建型模式
前端·设计模式
小林rush2 小时前
uni-app跨分包自定义组件引用解决方案
前端·javascript·vue.js
我的一行2 小时前
已有项目,接入pnpm + turbo
前端·vue.js
亮子AI2 小时前
【Svelte】怎样实现一个图片上传功能?
开发语言·前端·javascript·svelte
心.c2 小时前
为什么在 Vue 3 中 uni.createCanvasContext 画不出图?
前端·javascript·vue.js
咸鱼加辣2 小时前
【vue面试】ref和reactive
前端·javascript·vue.js
LYFlied2 小时前
【每日算法】LeetCode 104. 二叉树的最大深度
前端·算法·leetcode·面试·职场和发展
汤姆Tom2 小时前
前端转战后端:JavaScript 与 Java 对照学习指南(第五篇 —— 面向对象:类、接口与多态)
java·前端·后端