创建瀑布流（WaterFlow）
================

瀑布流[WaterFlow](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-waterflow)常用于展示图片信息，尤其在购物和资讯类应用中。

ArkUI提供了WaterFlow容器组件，用于构建瀑布流布局。WaterFlow组件支持条件渲染、循环渲染和懒加载等方式生成子组件。

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/7c/v3/68IEE6oFRSaxeDUAUZXQBw/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260612T073303Z&HW-CC-Expire=86400&HW-CC-Sign=B6A12CCF05142D2118CDE15FC608EA09F5AD92B6F39E69DCE44F3EF005FB82A6)

本文仅展示关键代码片段，可运行的完整代码请参考[WaterFlow示例代码](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-waterflow#示例)。

#### 布局与约束

瀑布流支持横向和纵向布局。在纵向布局中，可以通过[columnsTemplate](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-waterflow#columnstemplate)设置列数；在横向布局中，可以通过[rowsTemplate](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-waterflow#rowstemplate)设置行数。

在瀑布流的纵向布局中，第一行的子节点按从左到右顺序排列，从第二行开始，每个子节点将放置在当前总高度最小的列。如果多个列的总高度相同，则按照从左到右的顺序填充。如下图：

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/4f/v3/a6t6jZkZRWCcUayN_lMu3Q/zh-cn_image_0000002594827846.png?HW-CC-KV=V1&HW-CC-Date=20260612T073303Z&HW-CC-Expire=86400&HW-CC-Sign=964215493213E5395EEB79B96ED016EB72316F7614BA0260489EAA2C2173E953)

在瀑布流的横向布局中，每个子节点都会放置在当前总宽度最小的行。若多行总宽度相同，则按照从上到下的顺序进行填充。

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/59/v3/4tRnNOECQwmy5SFBtu9Anw/zh-cn_image_0000002625187317.png?HW-CC-KV=V1&HW-CC-Date=20260612T073303Z&HW-CC-Expire=86400&HW-CC-Sign=5D15E1E0B070F380B6ED5D91E725D4D12CFFA41FCC5E6C39349DF5150F0AE256)

#### 无限滚动

#### \[h2\]到达末尾时新增数据

瀑布流常用于无限滚动的信息流。可以在瀑布流组件到达末尾位置时触发的[onReachEnd](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-waterflow#onreachend)事件回调中对[LazyForEach](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-rendering-control-lazyforeach)增加新数据，并将footer做成正在加载新数据的样式（使用[LoadingProgress](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-loadingprogress)组件）。

@Builder
itemFoot() {
  Row() {
    LoadingProgress()
      .color(Color.Blue).height(50).aspectRatio(1).width('20%')
    // 请将$r('app.string.waterFlow\_text1')替换为实际资源文件，在本示例中该资源文件的value值为"正在加载 "
    Text($r('app.string.waterFlow\_text1'))
      .fontSize(20)
      .width('30%')
      .height(50)
      .align(Alignment.Center)
      .margin({ top: 2 })
  }.width('100%').justifyContent(FlexAlign.Center)
}

build() {
  NavDestination() {
    Column({ space: 12 }) {
      // ...
        WaterFlow({ footer: this.itemFoot(), layoutMode: WaterFlowLayoutMode.SLIDING\_WINDOW }) {
          LazyForEach(this.dataSource, (item: number) => {
            FlowItem() {
              ReusableFlowItem({ item: item })
            }
            .width('100%')
            .aspectRatio(this.itemHeightArray\[item % 100\] / this.itemWidthArray\[item%100\])
            .backgroundColor(this.colors\[item % 5\])
          }, (item: string) => item)
        }
        .columnsTemplate('1fr '.repeat(this.columns))
        .backgroundColor(0xFAEEE0)
        .width('100%')
        .height('100%')
        .layoutWeight(1)
        // 触底加载数据
        .onReachEnd(() => {
          setTimeout(() => {
            this.dataSource.addNewItems(100);
          }, 1000)
        })
      }
      // ...
  }
  .backgroundColor('#f1f2f3')
  // 请将$r('app.string.WaterFlowInfiniteScrolling\_title')替换为实际资源文件，在本示例中该资源文件的value值为"无限滚动（到达末尾时新增数据）"
  .title($r('app.string.WaterFlowInfiniteScrolling\_title'))
}

在此处应通过在数据末尾添加元素的方式来新增数据，不可直接修改dataArray后通过LazyForEach的[onDataReloaded](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-rendering-control-lazyforeach#ondatareloaded)方法通知瀑布流重新加载数据。

由于在瀑布流布局中，各子节点的高度不一致，下面的节点位置依赖于上面的节点，所以重新加载所有数据会触发整个瀑布流重新计算布局，可能会导致卡顿。在数据末尾增加数据后，应使用[onDataAdd](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-rendering-control-lazyforeach#ondataadd8)通知，以使瀑布流能够识别新增数据并继续加载，同时避免对已有数据进行重复处理。

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/16/v3/ZFoIeHBRRd2JebSL46rTxQ/zh-cn_image_0000002625107461.gif?HW-CC-KV=V1&HW-CC-Date=20260612T073303Z&HW-CC-Expire=86400&HW-CC-Sign=B1FDE8C254403E33E2BA4DA482E8546035FD332BA939D8CBB887E99BE4F5AA57)

#### \[h2\]提前新增数据

虽然在onReachEnd()触发时加载数据可以实现无限加载，但在滑动到底部会出现明显的停顿。

为了实现更加流畅的无限滑动，需要调整增加新数据的时机。比如可以在LazyForEach还剩余若干个数据未遍历的情况下提前加载新数据。以下代码通过在WaterFlow的[onScrollIndex](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-waterflow#onscrollindex11)中判断当前显示的最后一个子节点相对数据集终点的距离，并在合适时机提前加载新数据，实现了无停顿的无限滚动。

build() {
  NavDestination() {
    Column({ space: 12 }) {
      // ...
        WaterFlow({ layoutMode: WaterFlowLayoutMode.SLIDING\_WINDOW }) {
          LazyForEach(this.dataSource, (item: number) => {
            FlowItem() {
              ReusableFlowItem({ item: item })
            }
            .width('100%')
            .aspectRatio(this.itemHeightArray\[item % 100\] / this.itemWidthArray\[item%100\])
            .backgroundColor(this.colors\[item % 5\])
          }, (item: string) => item)
        }
        .columnsTemplate('1fr '.repeat(this.columns))
        .backgroundColor(0xFAEEE0)
        .width('100%')
        .height('100%')
        .layoutWeight(1)
        // 即将触底时提前增加数据
        .onScrollIndex((first: number, last: number) => {
          if (last + 20 >= this.dataSource.totalCount()) {
            setTimeout(() => {
              this.dataSource.addNewItems(100);
            }, 1000);
          }
        })
      }
      // ...
  }
  .backgroundColor('#f1f2f3')
  // 请将$r('app.string.WaterFlowInfiniteScrollingEarly\_title')替换为实际资源文件，在本示例中该资源文件的value值为"无限滚动（提前新增数据）"
  .title($r('app.string.WaterFlowInfiniteScrollingEarly\_title'))
}

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/e9/v3/TScjrN4pQpGj48CQVZjqEQ/zh-cn_image_0000002594667944.gif?HW-CC-KV=V1&HW-CC-Date=20260612T073303Z&HW-CC-Expire=86400&HW-CC-Sign=5AE51B881BF4D8B24D9FB32876D30FF58EE54F4F2B0165756A398FF5392B0E24)

#### 动态切换列数

通过动态调整瀑布流的列数，应用能够实现在列表模式与瀑布流模式间的切换，或适应屏幕宽度的变化。 若要动态设置列数，建议采用瀑布流的移动窗口布局模式，即取值为[WaterFlowLayoutMode](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-waterflow#waterflowlayoutmode12枚举说明)中的SLIDING\_WINDOW，这可以实现更快速的列数转换。

@Reusable
@Component
struct ReusableListItem {
  @State item: number = 0;

  aboutToReuse(params: Record<string, number>) {
    this.item = params.item;
  }

  build() {
    Row() {
      Image('res/waterFlow(' + this.item % 5 + ').JPG')
        .objectFit(ImageFit.Fill)
        .height(100)
        .aspectRatio(1)
      Text('N' + this.item).fontSize(12).height('16').layoutWeight(1).textAlign(TextAlign.Center)
    }
  }
}

@Entry
@Component
export struct WaterFlowDynamicSwitchover {
  // 通过状态变量设置列数，可以按需修改触发布局更新
  @State columns: number = 2;

  // ...
  build() {
    NavDestination() {
      Column({ space: 12 }) {
        // ...
          Column({ space: 2 }) {
            // 请将$r('app.string.waterFlow\_text2')替换为实际资源文件，在本示例中该资源文件的value值为"切换列数 "
            Button($r('app.string.waterFlow\_text2')).fontSize(20).onClick(() => {
              if (this.columns === 2) {
                this.columns = 1;
              } else {
                this.columns = 2;
              }
            })
            WaterFlow({ layoutMode: WaterFlowLayoutMode.SLIDING\_WINDOW }) {
              LazyForEach(this.dataSource, (item: number) => {
                FlowItem() {
                  if (this.columns === 1) {
                    ReusableListItem({ item: item })
                  } else {
                    ReusableFlowItem({ item: item })
                  }
                }
                .width('100%')
                .aspectRatio(this.columns === 2 ? this.itemHeightArray\[item % 100\] / this.itemWidthArray\[item % 100\] : 0)
                .backgroundColor(this.colors\[item % 5\])
              }, (item: string) => item)
            }
            .columnsTemplate('1fr '.repeat(this.columns))
            .backgroundColor(0xFAEEE0)
            .width('100%')
            .height('100%')
            .layoutWeight(1)
            // 即将触底时提前增加数据
            .onScrollIndex((first: number, last: number) => {
              if (last + 20 >= this.dataSource.totalCount()) {
                setTimeout(() => {
                  this.dataSource.addNewItems(100);
                }, 1000);
              }
            })
            // ...
          }
        }
        // ...
    }
    .backgroundColor('#f1f2f3')
    // 请将$r('app.string.WaterFlowDynamicSwitchover\_title')替换为实际资源文件，在本示例中该资源文件的value值为"动态切换列数"
    .title($r('app.string.WaterFlowDynamicSwitchover\_title'))
  }
}

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/6c/v3/h1MqETTzRQqifhchZd0OnQ/zh-cn_image_0000002594827872.gif?HW-CC-KV=V1&HW-CC-Date=20260612T073303Z&HW-CC-Expire=86400&HW-CC-Sign=AFD87EFD677050381556DF977204513A3160CE3F7E618E9B37121CF805EC211E)

#### 分组混合布局

许多应用界面在瀑布流上方包含其他内容，这类场景可通过在Scroll或List内部嵌套WaterFlow来实现。类似下图：

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/df/v3/6ZXrD8D4ScuwBs0-NnUBQA/zh-cn_image_0000002625187319.png?HW-CC-KV=V1&HW-CC-Date=20260612T073303Z&HW-CC-Expire=86400&HW-CC-Sign=60DC9F61F8148E9E214FB0ABEDB91FB85E7BA5478C61BD125F9D1839D026507D)

如果能够将不同部分的子节点整合到一个数据源中，那么通过设置[WaterFlowSections](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-waterflow#waterflowsections12)，可以在一个 WaterFlow 容器内实现混合布局。与嵌套滚动相比，这种方法可以简化滚动事件处理等应用逻辑。

每个瀑布流分组可以分别设置自己的列数、行间距、列间距、margin和子节点总数，如下代码可以实现上述效果：

@Entry
@Component
export struct WaterFlowGroupingMixing {
  minSize: number = 80;
  maxSize: number = 180;
  colors: number\[\] = \[0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F\];
  dataSource: WaterFlowDataSource = new WaterFlowDataSource(100);
  private itemWidthArray: number\[\] = \[\];
  private itemHeightArray: number\[\] = \[\];
  private gridItems: number\[\] = \[\];
  @State sections: WaterFlowSections = new WaterFlowSections();
  sectionMargin: Margin = {
    top: 10,
    left: 5,
    bottom: 10,
    right: 5
  };
  oneColumnSection: SectionOptions = {
    itemsCount: 1,
    crossCount: 1,
    columnsGap: 5,
    rowsGap: 10,
    margin: this.sectionMargin,
  };
  twoColumnSection: SectionOptions = {
    itemsCount: 98,
    crossCount: 2,
  };
  // 使用分组瀑布流时无法通过footer设置尾部组件，可以保留一个固定的分组作为footer
  lastSection: SectionOptions = {
    itemsCount: 1,
    crossCount: 1,
  };

  // 计算FlowItem宽/高
  getSize() {
    let ret = Math.floor(Math.random() \* this.maxSize);
    return (ret > this.minSize ? ret : this.minSize);
  }

  // 设置FlowItem的宽/高数组
  setItemSizeArray() {
    for (let i = 0; i < 100; i++) {
      this.itemWidthArray.push(this.getSize());
      this.itemHeightArray.push(this.getSize());
    }
  }

  aboutToAppear() {
    this.setItemSizeArray();
    for (let i = 0; i < 15; ++i) {
      this.gridItems.push(i);
    }
    // 所有分组的itemCount之和需要和WaterFlow下数据源的子节点总数相等，否则无法正常布局
    let sectionOptions: SectionOptions\[\] = \[this.oneColumnSection, this.twoColumnSection, this.lastSection\];
    this.sections.splice(0, 0, sectionOptions);
  }

  build() {
    NavDestination() {
      // ...
        WaterFlow({ layoutMode: WaterFlowLayoutMode.SLIDING\_WINDOW, sections: this.sections }) {
          LazyForEach(this.dataSource, (item: number) => {
            FlowItem() {
              if (item === 0) {
                Grid() {
                  ForEach(this.gridItems, (day: number) => {
                    GridItem() {
                      Text('GridItem').fontSize(14).height(16)
                    }.backgroundColor(0xFFC0CB)
                  }, (day: number) => day.toString())
                }
                .height('30%')
                .rowsGap(5)
                .columnsGap(5)
                .columnsTemplate('1fr '.repeat(5))
                .rowsTemplate('1fr '.repeat(3))
              } else {
                ReusableFlowItem({ item: item })
              }
            }
            .width('100%')
            .aspectRatio(item != 0 ? this.itemHeightArray\[item % 100\] / this.itemWidthArray\[item % 100\] : 0)
            .backgroundColor(item != 0 ? this.colors\[item % 5\] : Color.White)
          }, (item: string) => item)
        }
        .backgroundColor(0xFAEEE0)
        .height('100%')
        // 即将触底时提前增加数据
        .onScrollIndex((first: number, last: number) => {
          if (last + 20 >= this.dataSource.totalCount()) {
            setTimeout(() => {
              this.dataSource.addNewItems(100);
              // 增加数据后同步调整对应分组的itemCount
              this.twoColumnSection.itemsCount += 100;
              this.sections.update(1, this.twoColumnSection);
            }, 1000);
          }
        })
        .margin(10)
      }
      // ...
  }
}

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/04/v3/BMPp0lBAQ4WiO_NGN9ingw/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260612T073303Z&HW-CC-Expire=86400&HW-CC-Sign=B74F6067C59871E897D85E71E1BE5197E7C40D84601783DE634966DB5481936C)

使用分组混合布局时不支持单独设置footer，可以使用最后一个分组作为尾部组件。

增加或删除数据后需要同步修改对应分组的itemCount。

#### 示例代码

*   [实现WaterFlow瀑布流布局功能](https://gitcode.com/HarmonyOS_Samples/water-flow)
    
*   [主页瀑布流实现](https://gitcode.com/HarmonyOS-Cases/cases/blob/master/CommonAppDevelopment/feature/functionalscenes/README.md)