• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# WaterFlow
2
3<!--Kit: ArkUI-->
4<!--Subsystem: ArkUI-->
5<!--Owner: @fangyuhao-->
6<!--Designer: @zcdqs-->
7<!--Tester: @liuzhenshuo-->
8<!--Adviser: @HelloCrease-->
9
10瀑布流容器,由“行”和“列”分割的单元格所组成,通过容器自身的排列规则,将不同大小的“项目”自上而下,如瀑布般紧密布局。
11
12
13> **说明:**
14>
15> 该组件从API version 9 开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。
16>
17> WaterFlow组件支持展示瀑布流布局,不支持编辑模式和子元素拖动功能。
18>
19> 组件内部已绑定手势实现跟手滚动等功能,需要增加自定义手势操作时请参考[手势拦截增强](ts-gesture-blocking-enhancement.md)进行处理。
20## 子组件
21
22
23仅支持[FlowItem](ts-container-flowitem.md)子组件和自定义组件。自定义组件在WaterFlow下使用时,建议使用FlowItem作为自定组件的顶层组件,不建议给自定义组件设置属性和事件方法。
24支持通过渲染控制类型([if/else](../../../ui/state-management/arkts-rendering-control-ifelse.md)、[ForEach](../../../ui/state-management/arkts-rendering-control-foreach.md)、[LazyForEach](../../../ui/state-management/arkts-rendering-control-lazyforeach.md)和[Repeat](../../../ui/state-management/arkts-new-rendering-control-repeat.md))动态生成子组件,更推荐使用LazyForEach或Repeat以优化性能。
25
26>  **说明:**
27>
28>  WaterFlow子组件的visibility属性设置为None时不显示,但该子组件周围的columnsGap、rowsGap、margin仍会生效。
29>  在涉及大量子组件的情况下,建议采用懒加载、缓存数据、组件复用、固定宽高以及布局优化等方法,以提升性能和减少内存占用。最佳实践请参考[优化瀑布流加载慢丢帧问题](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-waterflow-performance-optimization)30>
31> 纵向布局时,WaterFlow会计算每一列中已放置子组件的累计高度,并将新子组件放入累计高度最小的那一列,以保持整体布局紧凑。
32>
33> 若多个列的高度相同,优先放入最左边的列。在RTL模式下,优先放入最右边的列。
34
35
36## 接口
37
38WaterFlow(options?:  WaterFlowOptions)
39
40创建瀑布流容器。
41
42**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
43
44**系统能力:** SystemCapability.ArkUI.ArkUI.Full
45
46**参数:**
47
48| 参数名 | 类型 | 必填 | 说明 |
49| -------- | -------- | -------- | -------- |
50| options |  [WaterFlowOptions](#waterflowoptions对象说明)| 否 | 瀑布流组件参数。 |
51
52
53## WaterFlowOptions对象说明
54
55瀑布流组件参数对象。
56
57**系统能力:** SystemCapability.ArkUI.ArkUI.Full
58
59| 名称     | 类型                                        | 只读 | 可选 | 说明                                     |
60| ---------- | ----------------------------------------------- | ------ | -- | -------------------------------------------- |
61| footer |  [CustomBuilder](ts-types.md#custombuilder8) | 否   | 是 | 设置WaterFlow尾部组件。<br/>**说明:** <br/>使用方法参见[示例1](#示例1使用基本瀑布流)。<br/>**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 |
62| footerContent<sup>18+</sup> | [ComponentContent](../js-apis-arkui-ComponentContent.md) | 否 | 是 | 设置WaterFlow尾部组件。<br/>该参数的优先级高于参数footer,即同时设置footer和footerContent时,以footerContent设置的组件为准。<br/>**原子化服务API:** 从API version 18开始,该接口支持在原子化服务中使用。 |
63| scroller | [Scroller](ts-container-scroll.md#scroller) | 否   | 是 | 可滚动组件的控制器,与可滚动组件绑定。<br/>**说明:** <br/>不允许和其他滚动类组件,如:[ArcList](ts-container-arclist.md)、[List](ts-container-list.md)、[Grid](ts-container-grid.md)、[Scroll](ts-container-scroll.md)和[WaterFlow](ts-container-waterflow.md)绑定同一个滚动控制对象。<br/>**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 |
64| sections<sup>12+</sup> |  [WaterFlowSections](#waterflowsections12) | 否   | 是 | 设置FlowItem分组,实现同一个瀑布流组件内部各分组使用不同列数混合布局。<br/>**说明:** <br/>1. 使用分组混合布局时会忽略columnsTemplate和rowsTemplate属性。<br/>2. 使用分组混合布局时不支持单独设置footer,可以使用最后一个分组作为尾部组件。<br/>**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。  |
65| layoutMode<sup>12+</sup> |[WaterFlowLayoutMode](#waterflowlayoutmode12枚举说明) | 否 | 是 | 设置WaterFlow的布局模式,根据使用场景选择更切合的模式。<br/>**说明:** <br/>默认值:[ALWAYS_TOP_DOWN](#waterflowlayoutmode12枚举说明)。<br/>**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
66
67
68## WaterFlowSections<sup>12+</sup>
69
70瀑布流分组信息。
71
72> **说明:**
73>
74> 使用splice、push、update修改分组信息后需要保证所有分组子节点总数与瀑布流实际子节点总数一致,否则会出现瀑布流因为不能正常布局而无法滑动的问题。
75
76### constructor
77
78constructor()
79
80创建一个瀑布流分组。
81
82**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
83
84**系统能力:** SystemCapability.ArkUI.ArkUI.Full
85
86### splice<sup>12+</sup>
87
88splice(start: number, deleteCount?: number, sections?: Array\<SectionOptions\>): boolean
89
90移除或者替换已存在的分组和/或添加新分组。
91
92**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
93
94**系统能力:** SystemCapability.ArkUI.ArkUI.Full
95
96**参数:**
97
98| 参数名   | 类型                            | 必填   | 说明                   |
99| ---- | ----------------------------- | ---- | -------------------- |
100| start | number | 是    | 从0开始计算的索引,会转换为整数,表示要开始改变分组的位置。<br/>**说明:** <br/>1. 如果索引是负数,则从末尾开始计算,使用`start + WaterFlowSections.length()`。<br/>2. 如果 `start < -WaterFlowSections.length()`,则使用0。<br/>3. 如果 `start >= WaterFlowSections.length()`,则在最后添加新分组。 |
101| deleteCount | number | 否    | 表示要从start开始删除的分组数量。<br/>**说明:** <br/>1. 如果省略了deleteCount,或者其值大于或等于由start指定的位置到WaterFlowSections末尾的分组数量,那么从start到WaterFlowSections末尾的所有分组将被删除。<br/>2. 如果deleteCount是0或者负数,则不会删除任何分组。 |
102| sections | Array<[SectionOptions](#sectionoptions12对象说明)> | 否    | 表示要从start开始加入的分组。如果不指定,`splice()`将只从瀑布流中删除分组。 |
103
104**返回值:**
105
106| 类型                                                         | 说明                                                         |
107| ------------------------------------------------------------ | ------------------------------------------------------------ |
108| boolean | 分组修改成功返回true;修改失败(要加入的分组中有任意分组的itemsCount不是非负数)返回false。 |
109
110
111### push<sup>12+</sup>
112
113push(section: SectionOptions): boolean
114
115将指定分组添加到瀑布流末尾。
116
117**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
118
119**系统能力:** SystemCapability.ArkUI.ArkUI.Full
120
121**参数:**
122
123| 参数名   | 类型                            | 必填   | 说明                   |
124| ---- | ----------------------------- | ---- | -------------------- |
125| section | [SectionOptions](#sectionoptions12对象说明) | 是    | 添加到瀑布流末尾的分组。 |
126
127**返回值:**
128
129| 类型                                                         | 说明                                                         |
130| ------------------------------------------------------------ | ------------------------------------------------------------ |
131| boolean | 分组添加成功返回true,添加失败(新分组的itemsCount不是非负数)返回false。 |
132
133### update<sup>12+</sup>
134
135update(sectionIndex: number, section: SectionOptions): boolean
136
137修改指定索引分组的配置信息。
138
139**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
140
141**系统能力:** SystemCapability.ArkUI.ArkUI.Full
142
143**参数:**
144
145| 参数名   | 类型                            | 必填   | 说明                   |
146| ---- | ----------------------------- | ---- | -------------------- |
147| sectionIndex | number | 是    | 从0开始计算的索引,会转换为整数,表示要修改的分组的位置。<br/>**说明:** <br/>1. 如果索引是负数,则从末尾开始计算,使用`sectionIndex + WaterFlowSections.length()`。<br/>2. 如果`sectionIndex < -WaterFlowSections.length()`,则使用0。<br/>3. 如果`sectionIndex >= WaterFlowSections.length()`,则在最后添加新分组。 |
148| section | [SectionOptions](#sectionoptions12对象说明) | 是    | 新的分组信息。 |
149
150**返回值:**
151
152| 类型                                                         | 说明                                                         |
153| ------------------------------------------------------------ | ------------------------------------------------------------ |
154| boolean | 分组是否更新成功,新分组的itemsCount不是非负数时返回false。 |
155
156### values<sup>12+</sup>
157
158values(): Array\<SectionOptions\>
159
160获取瀑布流中所有分组配置信息。
161
162**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
163
164**系统能力:** SystemCapability.ArkUI.ArkUI.Full
165
166**返回值:**
167
168| 类型                                                         | 说明                                                         |
169| ------------------------------------------------------------ | ------------------------------------------------------------ |
170| Array<[SectionOptions](#sectionoptions12对象说明)> | 瀑布流中所有分组配置信息。 |
171
172### length<sup>12+</sup>
173
174length(): number
175
176获取瀑布流中分组数量。
177
178**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
179
180**系统能力:** SystemCapability.ArkUI.ArkUI.Full
181
182**返回值:**
183
184| 类型                                                         | 说明                                                         |
185| ------------------------------------------------------------ | ------------------------------------------------------------ |
186| number | 瀑布流中分组数量。 |
187
188## SectionOptions<sup>12+</sup>对象说明
189
190FlowItem分组配置信息。
191
192**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
193
194**系统能力:** SystemCapability.ArkUI.ArkUI.Full
195
196| 名称 | 类型 | 只读 | 可选 | 说明 |
197|------|-----|-----|----|-----|
198| itemsCount | number | 否 | 否 | 分组中FlowItem数量,必须是非负数。若splice、push、update方法收到的分组中有分组的itemsCount小于0,则不会执行该方法。 避免使用itemsCount为0的分组,这可能导致布局计算异常。|
199| crossCount | number | 否 | 是 | 纵向布局时为列数,横向布局时为行数,默认值:1。小于1的按默认值处理。 |
200| columnsGap | [Dimension](ts-types.md#dimension10) | 否 | 是 | 该分组的列间距,不设置时使用瀑布流的columnsGap,设置非法值时使用0vp。 |
201| rowsGap | [Dimension](ts-types.md#dimension10) | 否 | 是 | 该分组的行间距,不设置时使用瀑布流的rowsGap,设置非法值时使用0vp。 |
202| margin | [Margin](ts-types.md#margin) \| [Dimension](ts-types.md#dimension10) | 否 | 是 | 该分组的外边距参数为Length类型时,四个方向外边距同时生效。<br>默认值:0<br>单位:vp<br>margin设置百分比时,上下左右外边距均以瀑布流的width作为基础值。 |
203| onGetItemMainSizeByIndex | [GetItemMainSizeByIndex](#getitemmainsizebyindex12) | 否 | 是 | 瀑布流组件布局过程中获取指定index的FlowItem的主轴大小,纵向瀑布流时为高度,横向瀑布流时为宽度,单位vp。<br/>**说明:** <br/>1. 同时使用onGetItemMainSizeByIndex和FlowItem的宽高属性时,主轴大小以onGetItemMainSizeByIndex返回结果为准,onGetItemMainSizeByIndex会覆盖FlowItem的主轴长度。<br/>2. 使用onGetItemMainSizeByIndex可以提高瀑布流跳转到指定位置或index时的效率,避免混用设置onGetItemMainSizeByIndex和未设置的分组,会导致布局异常。<br/>3. onGetItemMainSizeByIndex返回负数时FlowItem高度为0。 |
204
205
206## GetItemMainSizeByIndex<sup>12+</sup>
207
208type GetItemMainSizeByIndex = (index: number) => number
209
210根据index获取指定Item的主轴大小。
211
212**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
213
214**系统能力:** SystemCapability.ArkUI.ArkUI.Full
215
216**参数:**
217
218| 参数名   | 类型                            | 必填   | 说明                   |
219| ---- | ----------------------------- | ---- | -------------------- |
220| index | number | 是    | FlowItem在WaterFlow中的索引。<br/>取值范围:[0, 子节点总数-1] |
221
222**返回值:**
223
224| 类型                                                         | 说明                                                         |
225| ------------------------------------------------------------ | ------------------------------------------------------------ |
226| number | 指定index的FlowItem的主轴大小,纵向瀑布流时为高度,横向瀑布流时为宽度,单位vp。 |
227
228## WaterFlowLayoutMode<sup>12+</sup>枚举说明
229
230瀑布流组件布局模式枚举。
231
232**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
233
234**系统能力:** SystemCapability.ArkUI.ArkUI.Full
235
236| 名称 | 值 | 说明 |
237| ------ | ------ | -------------------- |
238| ALWAYS_TOP_DOWN | 0 | 默认的从上到下的布局模式。视窗内的FlowItem依赖视窗上方所有FlowItem的布局信息。因此跳转或切换列数时,需要计算出上方所有的FlowItem的布局信息。 |
239| SLIDING_WINDOW | 1 | 移动窗口式的布局模式。只考虑视窗内的布局信息,对视窗上方的FlowItem没有依赖关系,因此向后跳转或切换列数时只需要布局视窗内的FlowItem。建议优先采用该模式,尤其在应用需要支持屏幕旋转或动态切换列数的场景下。 <br/>**说明:** <br/>1. 无动画跳转到较远的位置时,会以目标位置为基准,向前或向后布局FlowItem。这之后如果滑回跳转前的位置,内容的布局效果可能和之前不一致。 这个效果会导致跳转后回滑到顶部时,顶部节点可能不对齐。所以该布局模式下会在滑动到顶部后自动调整布局,保证顶部对齐。在有多个分组的情况下,会在滑动结束时调整在视窗内的分组。<br/> 2. [scroller](#waterflowoptions对象说明)的[currentOffset](ts-container-scroll.md#currentoffset)接口返回的总偏移量在触发跳转或数据更新后不准确,在回滑到顶部时会重新校准。 <br/> 3. 如果在同一帧内调用跳转(如无动画的[scrollToIndex](ts-container-scroll.md#scrolltoindex)、[scrollEdge](ts-container-scroll.md#scrolledge))和输入偏移量(如滑动手势或滚动动画),两者都会生效。 <br/> 4. 调用无动画的[scrollToIndex](ts-container-scroll.md#scrolltoindex)进行跳转,如果跳转到较远位置(超过视窗内的FlowItem数量的位置)时,移动窗口模式对总偏移量进行估算。 <br/> 5. 仅在API version 18及以上版本中支持滚动条[scrollBar](ts-container-scrollable-common.md#scrollbar11)显示。低于此版本时,设置滚动条将不显示。|
240
241| 对比维度 | ALWAYS_TOP_DOWN (默认) | SLIDING_WINDOW |
242|---------|------------------------|----------------|
243| 适用场景 | 固定列数、简单瀑布流 | 动态列数、大数据量、屏幕旋转 |
244| 布局策略 | 从顶部开始完整布局 | 滑动窗口式布局 |
245| 性能特点 | 依赖上方所有 FlowItem | 只考虑视窗内布局 |
246| 跳转效率 | 需要计算上方所有布局 | 快速跳转,无需完整计算 |
247| 列数切换 | 需要重新计算全部布局 | 只重新布局视窗内容 |
248| 屏幕旋转 | 支持,但性能较差 | 支持,性能好 |
249| 滚动条显示 | 始终支持 | API 18+ 支持 |
250| 布局一致性 | 始终保持一致 | 跳转后可能不一致 |
251
252## 属性
253
254除支持[通用属性](ts-component-general-attributes.md)和[滚动组件通用属性](ts-container-scrollable-common.md#属性)外,还支持以下属性:
255> **说明:**
256>
257> WaterFlow组件使用通用属性[clip<sup>12+</sup>](ts-universal-attributes-sharp-clipping.md#clip12)和通用属性[clip<sup>18+</sup>](ts-universal-attributes-sharp-clipping.md#clip18)时默认值都为true。
258>
259> WaterFlow组件内容裁剪模式[ContentClipMode<sup>14+</sup>枚举说明](ts-container-scrollable-common.md#contentclipmode14枚举说明)为ContentClipMode.CONTENT_ONLY,padding区域会被裁剪不显示。
260
261### columnsTemplate
262
263columnsTemplate(value: string)
264
265设置当前瀑布流组件布局列的数量,不设置时默认1列。
266
267例如,'1fr 1fr 2fr' 是将父组件分3列,将父组件允许的宽分为4等份,第1列占1份,第2列占1份,第3列占2份。
268
269可使用columnsTemplate('repeat(auto-fill,track-size)')根据给定的列宽track-size自动计算列数,其中repeat、auto-fill为关键字,track-size为可设置的宽度,支持的单位包括px、vp、%或有效数字,默认单位为vp,使用方法参见示例2。
270
271**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
272
273**系统能力:** SystemCapability.ArkUI.ArkUI.Full
274
275**参数:**
276
277| 参数名 | 类型   | 必填 | 说明                                           |
278| ------ | ------ | ---- | ---------------------------------------------- |
279| value  | string | 是   | 当前瀑布流组件布局列的数量。<br/>默认值:'1fr' |
280
281### rowsTemplate
282
283rowsTemplate(value: string)
284
285设置当前瀑布流组件布局行的数量,不设置时默认1行。
286
287例如,'1fr 1fr 2fr'是将父组件分3行,将父组件允许的高分为4等份,第1行占1份,第2行占1份,第3行占2份。
288
289可使用rowsTemplate('repeat(auto-fill,track-size)')根据给定的行高track-size自动计算行数,其中repeat、auto-fill为关键字,track-size为可设置的高度,支持的单位包括px、vp、%或有效数字,默认单位为vp。
290
291**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
292
293**系统能力:** SystemCapability.ArkUI.ArkUI.Full
294
295**参数:**
296
297| 参数名 | 类型   | 必填 | 说明                                           |
298| ------ | ------ | ---- | ---------------------------------------------- |
299| value  | string | 是   | 当前瀑布流组件布局行的数量。<br/>默认值:'1fr' |
300
301### itemConstraintSize
302
303itemConstraintSize(value: ConstraintSizeOptions)
304
305设置约束尺寸,子组件布局时,进行尺寸范围限制。
306
307**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
308
309**系统能力:** SystemCapability.ArkUI.ArkUI.Full
310
311**参数:**
312
313| 参数名 | 类型                                                       | 必填 | 说明       |
314| ------ | ---------------------------------------------------------- | ---- | ---------- |
315| value  | [ConstraintSizeOptions](ts-types.md#constraintsizeoptions) | 是   | 约束尺寸。设置小于0的值,参数不生效。 <br/>**说明:**<br/>1.同时设置itemConstraintSize和FlowItem的[constraintSize](ts-universal-attributes-size.md#constraintsize)属性时,minWidth/minHeight会取其中的最大值,maxWidth/maxHeight会取其中的最小值,调整后的值作为FlowItem的constraintSize处理。<br/>2.只设置itemConstraintSize时,相当于对WaterFlow所有子组件设置了相同的constraintSize。<br/>3.itemConstraintSize通过以上两种方式转换成FlowItem的constraintSize后的生效规则与通用属性[constraintSize](./ts-universal-attributes-size.md#constraintsize)相同。|
316
317### columnsGap
318
319columnsGap(value: Length)
320
321设置列与列的间距。
322
323**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
324
325**系统能力:** SystemCapability.ArkUI.ArkUI.Full
326
327**参数:**
328
329| 参数名 | 类型                         | 必填 | 说明                          |
330| ------ | ---------------------------- | ---- | ----------------------------- |
331| value  | [Length](ts-types.md#length) | 是   | 列与列的间距。 <br/>默认值:0<br/>取值范围:[0, +∞) |
332
333### rowsGap
334
335rowsGap(value: Length)
336
337设置行与行的间距。
338
339**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
340
341**系统能力:** SystemCapability.ArkUI.ArkUI.Full
342
343**参数:**
344
345| 参数名 | 类型                         | 必填 | 说明                          |
346| ------ | ---------------------------- | ---- | ----------------------------- |
347| value  | [Length](ts-types.md#length) | 是   | 行与行的间距。 <br/>默认值:0<br/>取值范围:[0, +∞) |
348
349### layoutDirection
350
351layoutDirection(value: FlexDirection)
352
353设置布局的主轴方向。
354
355**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
356
357**系统能力:** SystemCapability.ArkUI.ArkUI.Full
358
359**参数:**
360
361| 参数名 | 类型                                                | 必填 | 说明                                              |
362| ------ | --------------------------------------------------- | ---- | ------------------------------------------------- |
363| value  | [FlexDirection](ts-appendix-enums.md#flexdirection) | 是   | 布局的主轴方向。<br/>默认值:FlexDirection.Column |
364
365layoutDirection优先级高于rowsTemplate和columnsTemplate。根据layoutDirection设置情况,分为以下三种设置模式:
366
367- layoutDirection设置纵向布局(FlexDirection.ColumnFlexDirection.ColumnReverse368
369  此时columnsTemplate有效(如果未设置,取默认值)。例如columnsTemplate设置为"1fr 1fr"、rowsTemplate设置为"1fr 1fr 1fr"时,瀑布流组件纵向布局,辅轴均分成横向2列。
370
371- layoutDirection设置横向布局(FlexDirection.RowFlexDirection.RowReverse372
373  此时rowsTemplate有效(如果未设置,取默认值)。例如columnsTemplate设置为"1fr 1fr"、rowsTemplate设置为"1fr 1fr 1fr"时,瀑布流组件横向布局,辅轴均分成纵向3列。
374
375- layoutDirection未设置布局方向
376
377  布局方向为layoutDirection的默认值:FlexDirection.Column,此时columnsTemplate有效。例如columnsTemplate设置为"1fr 1fr"、rowsTemplate设置为"1fr 1fr 1fr"时,瀑布流组件纵向布局,辅轴均分成横向2列。
378
379### enableScrollInteraction<sup>10+</sup>
380
381enableScrollInteraction(value: boolean)
382
383设置是否支持滚动手势。
384
385**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
386
387**系统能力:** SystemCapability.ArkUI.ArkUI.Full
388
389**参数:**
390
391| 参数名 | 类型    | 必填 | 说明                                |
392| ------ | ------- | ---- | ----------------------------------- |
393| value  | boolean | 是   | 是否支持滚动手势。设置为true时可以通过手指或者鼠标滚动,设置为false时无法通过手指或者鼠标滚动,但不影响控制器[Scroller](ts-container-scroll.md#scroller)的滚动接口。<br/>默认值:true |
394
395> **说明:**
396>
397> 组件无法通过鼠标按下拖动操作进行滚动。
398
399### nestedScroll<sup>10+</sup>
400
401nestedScroll(value: NestedScrollOptions)
402
403设置前后两个方向的嵌套滚动模式,实现与父组件的滚动联动。
404
405**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
406
407**系统能力:** SystemCapability.ArkUI.ArkUI.Full
408
409**参数:**
410
411| 参数名 | 类型                                                         | 必填 | 说明           |
412| ------ | ------------------------------------------------------------ | ---- | -------------- |
413| value  | [NestedScrollOptions](ts-container-scrollable-common.md#nestedscrolloptions10对象说明) | 是   | 嵌套滚动选项。 |
414
415### friction<sup>10+</sup>
416
417friction(value: number | Resource)
418
419设置摩擦系数,手动划动滚动区域时生效,仅影响惯性滚动过程,对惯性滚动过程中的链式效果有间接影响。
420
421**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
422
423**系统能力:** SystemCapability.ArkUI.ArkUI.Full
424
425**参数:**
426
427| 参数名 | 类型                                                 | 必填 | 说明                                                      |
428| ------ | ---------------------------------------------------- | ---- | --------------------------------------------------------- |
429| value  | number&nbsp;\|&nbsp;[Resource](ts-types.md#resource) | 是   | 摩擦系数。<br/>默认值:非可穿戴设备为0.6,可穿戴设备为0.9。<br/>从API version 11开始,非可穿戴设备默认值为0.7。<br/>从API version 12开始,非可穿戴设备默认值为0.75。<br/>取值范围:(0, +∞),设置为小于等于0的值时,按默认值处理。 |
430
431### cachedCount<sup>11+</sup>
432
433cachedCount(value: number)
434
435设置预加载的FlowItem数量。
436
437只在[LazyForEach](../../../ui/state-management/arkts-rendering-control-lazyforeach.md)和开启了virtualScroll开关的[Repeat](../../../ui/state-management/arkts-new-rendering-control-repeat.md)中生效,超出显示及缓存范围的FlowItem会被释放。
438
439**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
440
441**系统能力:** SystemCapability.ArkUI.ArkUI.Full
442
443**参数:**
444
445| 参数名 | 类型   | 必填 | 说明                                                         |
446| ------ | ------ | ---- | ------------------------------------------------------------ |
447| value  | number | 是   | 预加载的FlowItem的数量。 <br/>默认值:根据屏幕内显示的节点个数设置,最大值为16。<br/>取值范围:[0, +∞),设置为小于0的值时,按1处理。 |
448
449### cachedCount<sup>14+</sup>
450
451cachedCount(count: number, show: boolean)
452
453设置预加载的FlowItem数量,并配置是否显示预加载节点。
454
455配合[裁剪](ts-universal-attributes-sharp-clipping.md#clip12)或[内容裁剪](ts-container-scrollable-common.md#clipcontent14)属性可以显示出预加载节点。
456
457只在[LazyForEach](../../../ui/state-management/arkts-rendering-control-lazyforeach.md)和开启了virtualScroll开关的[Repeat](../../../ui/state-management/arkts-new-rendering-control-repeat.md)中生效,超出显示及缓存范围的FlowItem会被释放。
458
459**原子化服务API:** 从API version 14开始,该接口支持在原子化服务中使用。
460
461**系统能力:** SystemCapability.ArkUI.ArkUI.Full
462
463**参数:**
464
465| 参数名 | 类型   | 必填 | 说明                                     |
466| ------ | ------ | ---- | ---------------------------------------- |
467| count | number | 是   | 预加载的FlowItem的数量。 <br/>默认值:根据屏幕内显示的节点个数设置,最大值为16。<br/>取值范围:[0, +∞),设置为小于0的值时,按1处理。 |
468| show  | boolean | 是   | 被预加载的FlowItem是否需要显示。设置为true时显示预加载的FlowItem,设置为false时不显示预加载的FlowItem。 <br/> 默认值:false |
469
470### syncLoad<sup>20+</sup>
471
472syncLoad(enable: boolean)
473
474设置是否同步加载WaterFlow区域内所有子组件。
475
476**原子化服务API:** 从API version 20开始,该接口支持在原子化服务中使用。
477
478**系统能力:** SystemCapability.ArkUI.ArkUI.Full
479
480**参数:**
481
482| 参数名 | 类型                                                         | 必填 | 说明                                                         |
483| ------ | ------------------------------------------------------------ | ---- | ------------------------------------------------------------ |
484| enable   | boolean | 是   | 是否同步加载WaterFlow区域内所有子组件。<br/>true表示同步加载,false表示异步加载。默认值:true。<br/>**说明:** <br/>设置为false时,在首次显示、不带动画scrollToIndex跳转场景,若当帧布局耗时超过50ms,会将WaterFlow区域内尚未布局的子组件延后到下一帧进行布局。 |
485
486## 事件
487
488除支持[通用事件](ts-component-general-events.md)和[滚动组件通用事件](ts-container-scrollable-common.md#事件)外,还支持以下事件:
489
490### onReachStart
491
492onReachStart(event: () => void)
493
494瀑布流内容到达起始位置时触发。
495
496**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
497
498**系统能力:** SystemCapability.ArkUI.ArkUI.Full
499
500**参数:**
501
502| 参数名 | 类型 | 必填 | 说明 |
503| ------ | ------ | ------ | ------|
504| event | () => void | 是 | 瀑布流内容到达起始位置时触发的回调。 |
505
506### onReachEnd
507
508onReachEnd(event: () => void)
509
510瀑布流内容到达末尾位置时触发。
511
512**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
513
514**系统能力:** SystemCapability.ArkUI.ArkUI.Full
515
516**参数:**
517
518| 参数名 | 类型 | 必填 | 说明 |
519| ------ | ------ | ------ | ------|
520| event | () => void | 是 | 瀑布流内容到达末尾位置时触发的回调。 |
521
522### onScrollFrameBegin<sup>10+</sup>
523
524onScrollFrameBegin(event: OnScrollFrameBeginCallback)
525
526该接口回调时,事件参数传入即将发生的滑动量,事件处理函数中可根据应用场景计算实际需要的滑动量并作为事件处理函数的返回值返回,瀑布流将按照返回值的实际滑动量进行滑动。
527
528满足以下任一条件时触发该事件:
529
5301. 用户交互(如手指滑动、键鼠操作等)触发滚动。
5312. WaterFlow惯性滚动。
5323. 调用[fling](ts-container-scroll.md#fling12)接口触发滚动。
533
534不触发该事件的条件:
535
5361. 调用除[fling](ts-container-scroll.md#fling12)接口外的其他滚动控制接口。
5372. 越界回弹。
5383. 拖动滚动条。
539
540**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
541
542**系统能力:** SystemCapability.ArkUI.ArkUI.Full
543
544**参数:**
545
546| 参数名 | 类型                                                    | 必填 | 说明                       |
547| ------ | ------------------------------------------------------- | ---- | -------------------------- |
548| event | [OnScrollFrameBeginCallback](ts-container-scroll.md#onscrollframebegincallback18)   | 是   | 每帧滚动开始回调函数。 |
549
550### onScrollIndex<sup>11+</sup>
551
552onScrollIndex(event: (first: number, last: number) => void)
553
554当前瀑布流显示的起始位置/终止位置的子组件发生变化时触发。瀑布流初始化时会触发一次。
555
556瀑布流显示区域上第一个子组件/最后一个组件的索引值有变化就会触发。
557
558**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
559
560**系统能力:** SystemCapability.ArkUI.ArkUI.Full
561
562**参数:**
563
564| 参数名 | 类型   | 必填 | 说明                                  |
565| ------ | ------ | ---- | ------------------------------------- |
566| first  | number | 是   | 当前显示的瀑布流起始位置的索引值。<br/>取值范围:[0, 子节点总数-1] |
567| last   | number | 是   | 当前显示的瀑布流终止位置的索引值。<br/>取值范围:[0, 子节点总数-1] |
568
569通过`last`参数可以判断是否“继续加载数据”,参考[示例3使用分组](#示例3使用分组)中"即将触底时提前增加数据"的处理逻辑。
570
571当WaterFlow列表为空时,使用不同的WaterFlowOptions参数会导致onScrollIndex事件的返回值有所不同。具体差异请参见下表:
572
573| layoutMode | sections | first | last |
574| --- | --- | --- | --- |
575| ALWAYS_TOP_DOWN | 无 | 0 | 0 |
576| ALWAYS_TOP_DOWN | 有 | 0 | -1 |
577| SLIDING_WINDOW | 可选 | 1000000 | -1 |
578
579
580## UIWaterFlowEvent<sup>19+</sup>
581frameNode中[getEvent('WaterFlow')](../js-apis-arkui-frameNode.md#geteventwaterflow19)方法的返回值,可用于给WaterFlow节点设置滚动事件。
582
583UIWaterFlowEvent继承于[UIScrollableCommonEvent](./ts-container-scrollable-common.md#uiscrollablecommonevent19)。
584
585### setOnWillScroll<sup>19+</sup>
586
587setOnWillScroll(callback:  OnWillScrollCallback | undefined): void
588
589设置[onWillScroll](./ts-container-scrollable-common.md#onwillscroll12)事件的回调。
590
591方法入参为undefined时,会重置事件回调。
592
593**原子化服务API:** 从API version 19开始,该接口支持在原子化服务中使用。
594
595**系统能力:** SystemCapability.ArkUI.ArkUI.Full
596
597**参数:**
598
599| 参数名 | 类型   | 必填 | 说明                       |
600| ------ | ------ | ---- | -------------------------- |
601| callback  | [OnWillScrollCallback](./ts-container-scrollable-common.md#onwillscrollcallback12)&nbsp;\|&nbsp;undefined | 是   | onWillScroll事件的回调函数。 |
602
603### setOnDidScroll<sup>19+</sup>
604
605setOnDidScroll(callback: OnScrollCallback | undefined): void
606
607设置[onDidScroll](./ts-container-scrollable-common.md#ondidscroll12)事件的回调。
608
609方法入参为undefined时,会重置事件回调。
610
611**原子化服务API:** 从API version 19开始,该接口支持在原子化服务中使用。
612
613**系统能力:** SystemCapability.ArkUI.ArkUI.Full
614
615**参数:**
616
617| 参数名 | 类型   | 必填 | 说明                       |
618| ------ | ------ | ---- | -------------------------- |
619| callback  | [OnScrollCallback](./ts-container-scrollable-common.md#onscrollcallback12)&nbsp;\|&nbsp;undefined | 是   | onDidScroll事件的回调函数。 |
620
621### setOnScrollIndex<sup>19+</sup>
622
623setOnScrollIndex(callback: OnWaterFlowScrollIndexCallback | undefined): void
624
625设置[onScrollIndex](#onscrollindex11)事件的回调。
626
627方法入参为undefined时,会重置事件回调。
628
629**原子化服务API:** 从API version 19开始,该接口支持在原子化服务中使用。
630
631**系统能力:** SystemCapability.ArkUI.ArkUI.Full
632
633**参数:**
634
635| 参数名 | 类型   | 必填 | 说明                       |
636| ------ | ------ | ---- | -------------------------- |
637| callback  | [OnWaterFlowScrollIndexCallback](#onwaterflowscrollindexcallback19)&nbsp;\|&nbsp;undefined | 是   | onScrollIndex事件的回调函数。 |
638
639## OnWaterFlowScrollIndexCallback<sup>19+</sup>
640type OnWaterFlowScrollIndexCallback = (first: number, last: number) => void
641
642WaterFlow组件可见区域item变化事件的回调类型。
643
644**原子化服务API:** 从API version 19开始,该接口支持在原子化服务中使用。
645
646**系统能力:** SystemCapability.ArkUI.ArkUI.Full
647
648**参数:**
649
650| 参数名 | 类型   | 必填 | 说明                                  |
651| ------ | ------ | ---- | ------------------------------------- |
652| first  | number | 是   | 当前显示的瀑布流起始位置的索引值。 |
653| last   | number | 是   | 当前显示的瀑布流终止位置的索引值。 |
654
655## 示例
656
657### 示例1(使用基本瀑布流)
658该示例展示了WaterFlow组件数据加载处理、属性设置和事件回调等基本使用场景。
659
660WaterFlowDataSource实现了LazyForEach数据源接口[IDataSource](ts-rendering-control-lazyforeach.md#idatasource),用于通过LazyForEach给WaterFlow提供子组件。
661
662<!--code_no_check-->
663```ts
664// WaterFlowDataSource.ets
665
666// 实现IDataSource接口的对象,用于瀑布流组件加载数据
667export class WaterFlowDataSource implements IDataSource {
668  private dataArray: number[] = [];
669  private listeners: DataChangeListener[] = [];
670
671  constructor() {
672    for (let i = 0; i < 100; i++) {
673      this.dataArray.push(i);
674    }
675  }
676
677  // 获取索引对应的数据
678  public getData(index: number): number {
679    return this.dataArray[index];
680  }
681
682  // 通知控制器数据重新加载
683  notifyDataReload(): void {
684    this.listeners.forEach(listener => {
685      listener.onDataReloaded();
686    })
687  }
688
689  // 通知控制器数据增加
690  notifyDataAdd(index: number): void {
691    this.listeners.forEach(listener => {
692      listener.onDataAdd(index);
693    })
694  }
695
696  // 通知控制器数据变化
697  notifyDataChange(index: number): void {
698    this.listeners.forEach(listener => {
699      listener.onDataChange(index);
700    })
701  }
702
703  // 通知控制器数据删除
704  notifyDataDelete(index: number): void {
705    this.listeners.forEach(listener => {
706      listener.onDataDelete(index);
707    })
708  }
709
710  // 通知控制器数据位置变化
711  notifyDataMove(from: number, to: number): void {
712    this.listeners.forEach(listener => {
713      listener.onDataMove(from, to);
714    })
715  }
716
717  //通知控制器数据批量修改
718  notifyDatasetChange(operations: DataOperation[]): void {
719    this.listeners.forEach(listener => {
720      listener.onDatasetChange(operations);
721    })
722  }
723
724  // 获取数据总数
725  public totalCount(): number {
726    return this.dataArray.length;
727  }
728
729  // 注册改变数据的控制器
730  registerDataChangeListener(listener: DataChangeListener): void {
731    if (this.listeners.indexOf(listener) < 0) {
732      this.listeners.push(listener);
733    }
734  }
735
736  // 注销改变数据的控制器
737  unregisterDataChangeListener(listener: DataChangeListener): void {
738    const pos = this.listeners.indexOf(listener);
739    if (pos >= 0) {
740      this.listeners.splice(pos, 1);
741    }
742  }
743
744  // 增加数据
745  public add1stItem(): void {
746    this.dataArray.splice(0, 0, this.dataArray.length);
747    this.notifyDataAdd(0);
748  }
749
750  // 在数据尾部增加一个元素
751  public addLastItem(): void {
752    this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length);
753    this.notifyDataAdd(this.dataArray.length - 1);
754  }
755
756  // 在指定索引位置增加一个元素
757  public addItem(index: number): void {
758    this.dataArray.splice(index, 0, this.dataArray.length);
759    this.notifyDataAdd(index);
760  }
761
762  // 删除第一个元素
763  public delete1stItem(): void {
764    this.dataArray.splice(0, 1);
765    this.notifyDataDelete(0);
766  }
767
768  // 删除第二个元素
769  public delete2ndItem(): void {
770    this.dataArray.splice(1, 1);
771    this.notifyDataDelete(1);
772  }
773
774  // 删除最后一个元素
775  public deleteLastItem(): void {
776    this.dataArray.splice(-1, 1);
777    this.notifyDataDelete(this.dataArray.length);
778  }
779
780  // 在指定索引位置删除一个元素
781  public deleteItem(index: number): void {
782    this.dataArray.splice(index, 1);
783    this.notifyDataDelete(index);
784  }
785
786  // 重新加载数据
787  public reload(): void {
788    this.dataArray.splice(1, 1);
789    this.dataArray.splice(3, 2);
790    this.notifyDataReload();
791  }
792
793  // 在数据尾部增加count个元素
794  public addNewItems(count: number): void {
795    let len = this.dataArray.length;
796    for (let i = 0; i < count; i++) {
797      this.dataArray.push(this.dataArray[len - 1] + i + 1);
798      this.notifyDataAdd(this.dataArray.length - 1);
799    }
800  }
801
802  // 刷新所有元素
803  public refreshItems(): void {
804    let newDataArray: number[] = [];
805    for (let i = 0; i < 100; i++) {
806      newDataArray.push(this.dataArray[0] + i + 1000);
807    }
808    this.dataArray = newDataArray;
809    this.notifyDataReload();
810  }
811}
812```
813
814<!--code_no_check-->
815```ts
816// Index.ets
817import { WaterFlowDataSource } from './WaterFlowDataSource';
818
819enum FooterState {
820  Loading = 0,
821  End = 1
822}
823
824@Entry
825@Component
826struct WaterFlowDemo {
827  @State minSize: number = 80;
828  @State maxSize: number = 180;
829  @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F];
830  @State footerState: FooterState = FooterState.Loading;
831  scroller: Scroller = new Scroller();
832  dataSource: WaterFlowDataSource = new WaterFlowDataSource();
833  private itemWidthArray: number[] = [];
834  private itemHeightArray: number[] = [];
835
836  // 计算FlowItem宽/高
837  getSize() {
838    let ret = Math.floor(Math.random() * this.maxSize);
839    return (ret > this.minSize ? ret : this.minSize);
840  }
841
842  // 设置FlowItem的宽/高数组
843  setItemSizeArray() {
844    for (let i = 0; i < 100; i++) {
845      this.itemWidthArray.push(this.getSize());
846      this.itemHeightArray.push(this.getSize());
847    }
848  }
849
850  // 组件生命周期:在组件即将出现时初始化尺寸数组
851  aboutToAppear() {
852    this.setItemSizeArray();
853  }
854
855  @Builder
856  itemFoot() {
857    // 注意:不要直接用IfElse节点作为footer的根节点
858    // 必须在外面使用(Column/Row/Stack等)容器包裹,确保布局正确
859    Column() {
860      if (this.footerState == FooterState.Loading) {
861        Text(`加载中...`)
862          .fontSize(10)
863          .backgroundColor(Color.Red)
864          .width(50)
865          .height(50)
866          .align(Alignment.Center)
867          .margin({ top: 2 })
868      } else if (this.footerState == FooterState.End) {
869        Text(`到底啦...`)
870          .fontSize(10)
871          .backgroundColor(Color.Red)
872          .width(50)
873          .height(50)
874          .align(Alignment.Center)
875          .margin({ top: 2 })
876      } else {
877        Text(`Footer`)
878          .fontSize(10)
879          .backgroundColor(Color.Red)
880          .width(50)
881          .height(50)
882          .align(Alignment.Center)
883          .margin({ top: 2 })
884      }
885    }
886  }
887
888  build() {
889    Column({ space: 2 }) {
890      WaterFlow({ footer: this.itemFoot() }) {
891        LazyForEach(this.dataSource, (item: number) => {
892          FlowItem() {
893            Column() {
894              Text("N" + item).fontSize(12).height('16')
895              // 注意:需要确保对应的jpg文件存在才会正常显示
896              Image('res/waterFlowTest(' + item % 5 + ').jpg')
897                .objectFit(ImageFit.Fill)
898                .width('100%')
899                .layoutWeight(1)
900            }
901          }
902          .width('100%')
903          .height(this.itemHeightArray[item % 100])
904          .backgroundColor(this.colors[item % this.colors.length])
905        }, (item: string) => item)
906      }
907      .columnsTemplate("1fr 1fr")    // 设置2列等宽布局
908      .columnsGap(10)
909      .rowsGap(5)
910      .backgroundColor(0xFAEEE0)
911      .width('100%')
912      .height('100%')
913      // 触底加载数据:滚动到底部时触发分页加载
914      .onReachEnd(() => {
915        console.info("onReachEnd")
916
917        // 模拟分页加载:当数据超过200条时停止加载
918        if (this.dataSource.totalCount() > 200) {
919          this.footerState = FooterState.End;
920          return;
921        }
922        setTimeout(() => {
923          for (let i = 0; i < 100; i++) {
924            this.dataSource.addLastItem();
925          }
926        }, 1000)
927      })
928      .onReachStart(() => {
929        // 滚动到顶部时触发
930        console.info('waterFlow reach start');
931      })
932      .onScrollStart(() => {
933        // 开始滚动时触发
934        console.info('waterFlow scroll start');
935      })
936      .onScrollStop(() => {
937        // 停止滚动时触发
938        console.info('waterFlow scroll stop');
939      })
940      .onScrollFrameBegin((offset: number, state: ScrollState) => {
941        // 滚动帧开始时触发:可以控制滚动行为
942        // offset:滚动偏移量,state:滚动状态
943        console.info('waterFlow scrollFrameBegin offset: ' + offset + ' state: ' + state.toString());
944        return { offsetRemain: offset };  // 返回开发者期望的实际滚动偏移量
945      })
946    }
947  }
948}
949```
950
951![zh-cn_image_WaterFlow.gif](figures/waterflow-perf-demo.gif)
952
953### 示例2(自动计算列数)
954该示例通过auto-fill实现了自动计算列数的效果。
955
956WaterFlowDataSource说明及完整代码参考[示例1使用基本瀑布流](#示例1使用基本瀑布流)。
957
958<!--code_no_check-->
959```ts
960// Index.ets
961import { WaterFlowDataSource } from './WaterFlowDataSource';
962
963@Entry
964@Component
965struct WaterFlowDemo {
966  @State minSize: number = 80;
967  @State maxSize: number = 180;
968  @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F];
969  dataSource: WaterFlowDataSource = new WaterFlowDataSource();
970  private itemWidthArray: number[] = [];
971  private itemHeightArray: number[] = [];
972
973  // 计算FlowItem宽/高
974  getSize() {
975    let ret = Math.floor(Math.random() * this.maxSize);
976    return (ret > this.minSize ? ret : this.minSize);
977  }
978
979  // 设置FlowItem宽/高数组
980  setItemSizeArray() {
981    for (let i = 0; i < 100; i++) {
982      this.itemWidthArray.push(this.getSize());
983      this.itemHeightArray.push(this.getSize());
984    }
985  }
986
987  // 组件生命周期:在组件即将出现时初始化尺寸数组
988  aboutToAppear() {
989    this.setItemSizeArray();
990  }
991
992  build() {
993    Column({ space: 2 }) {
994      WaterFlow() {
995        LazyForEach(this.dataSource, (item: number) => {
996          FlowItem() {
997            Column() {
998              Text("N" + item).fontSize(12).height('16')
999              // 存在对应的jpg文件才会显示图片
1000              Image('res/waterFlowTest(' + item % 5 + ').jpg')
1001            }
1002          }
1003          .width('100%')
1004          .height(this.itemHeightArray[item % 100])
1005          .backgroundColor(this.colors[item % this.colors.length])
1006        }, (item: string) => item)
1007      }
1008      // auto-fill自动计算列数
1009      // 'repeat(auto-fill,80)' 表示:根据容器宽度自动计算能放下多少个80px宽的列
1010      // 例如:容器宽度400px,则自动计算为5列(400÷80=5)
1011      .columnsTemplate('repeat(auto-fill,80)')
1012      .columnsGap(10)
1013      .rowsGap(5)
1014      .padding({left:5})
1015      .backgroundColor(0xFAEEE0)
1016      .width('100%')
1017      .height('100%')
1018    }
1019  }
1020}
1021```
1022
1023![waterflow_auto-fill.png](figures/waterflow_auto-fill.png)
1024
1025
1026### 示例3(使用分组)
1027该示例展示了分组的初始化以及splice、push、update、values、length等接口的不同效果。
1028如果配合状态管理V2使用,详情见:[WaterFlow与makeObserved](../../../ui/state-management/arkts-v1-v2-migration-application-and-others.md#滑动组件)。
1029
1030WaterFlowDataSource说明及完整代码参考[示例1使用基本瀑布流](#示例1使用基本瀑布流)。
1031
1032<!--code_no_check-->
1033```ts
1034// Index.ets
1035import { WaterFlowDataSource } from './WaterFlowDataSource';
1036
1037// 可复用组件:优化性能,减少组件创建销毁开销
1038@Reusable
1039@Component
1040struct ReusableFlowItem {
1041  @State item: number = 0;
1042
1043  // 组件复用生命周期:从复用缓存中取出时调用
1044  // 用于更新组件状态,显示新的内容
1045  aboutToReuse(params: Record<string, number>) {
1046    this.item = params.item;
1047    console.info('Reuse item:' + this.item);
1048  }
1049
1050  // 组件生命周期:初始化尺寸数组和分组配置
1051  aboutToAppear() {
1052    console.info('new item:' + this.item);
1053  }
1054
1055  build() {
1056    Column() {
1057      // 注意:需要确保对应的jpg文件存在才会正常显示
1058      Image('res/waterFlowTest(' + this.item % 5 + ').jpg')
1059        .overlay('N' + this.item, { align: Alignment.Top })
1060        .objectFit(ImageFit.Fill)
1061        .width('100%')
1062        .layoutWeight(1)
1063    }
1064  }
1065}
1066
1067@Entry
1068@Component
1069struct WaterFlowDemo {
1070  minSize: number = 80;
1071  maxSize: number = 180;
1072  colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F];
1073  scroller: Scroller = new Scroller();
1074  dataSource: WaterFlowDataSource = new WaterFlowDataSource();
1075  dataCount: number = this.dataSource.totalCount();
1076  private itemWidthArray: number[] = [];
1077  private itemHeightArray: number[] = [];
1078  // 分组管理:WaterFlow的核心特性,支持不同区域使用不同列数
1079  @State sections: WaterFlowSections = new WaterFlowSections();
1080  // 分组边距配置:统一的外边距设置
1081  sectionMargin: Margin = { top: 10, left: 5, bottom: 10, right: 5 };
1082
1083  oneColumnSection: SectionOptions = {
1084    itemsCount: 4,                     // 该分组包含4个FlowItem
1085    crossCount: 1,                     // 使用1列布局
1086    columnsGap: '5vp',
1087    rowsGap: 10,
1088    margin: this.sectionMargin,
1089    // 回调函数:动态设置每个item的高度
1090    onGetItemMainSizeByIndex: (index: number) => {
1091      return this.itemHeightArray[index % 100];
1092    }
1093  };
1094
1095  // 第二种分组:双列布局,适合展示列表内容
1096  twoColumnSection: SectionOptions = {
1097    itemsCount: 2,                     // 该分组包含2个FlowItem
1098    crossCount: 2,                     // 使用2列布局
1099    // 回调函数:固定高度100px
1100    onGetItemMainSizeByIndex: (index: number) => {
1101      return 100;
1102    }
1103  };
1104
1105  // 最后一个分组:用于处理剩余数据
1106  lastSection: SectionOptions = {
1107    itemsCount: 20,                    // 该分组包含20个FlowItem
1108    crossCount: 2,                     // 使用2列布局
1109    // 回调函数:使用随机高度
1110    onGetItemMainSizeByIndex: (index: number) => {
1111      return this.itemHeightArray[index % 100];
1112    }
1113  };
1114
1115  // 计算FlowItem高度
1116  getSize() {
1117    let ret = Math.floor(Math.random() * this.maxSize);
1118    return (ret > this.minSize ? ret : this.minSize);
1119  }
1120
1121  // 设置FlowItem的高度数组
1122  setItemSizeArray() {
1123    for (let i = 0; i < 100; i++) {
1124      this.itemHeightArray.push(this.getSize());
1125    }
1126  }
1127
1128  // 组件生命周期:初始化数据和恢复上次的列数设置
1129  aboutToAppear() {
1130    this.setItemSizeArray();
1131
1132    // 初始化瀑布流分组信息:交替使用单列和双列布局
1133    let sectionOptions: SectionOptions[] = [];
1134    let count = 0;                     // 已分配的FlowItem数量计数
1135    let oneOrTwo = 0;                  // 用于交替选择分组类型
1136
1137    while (count < this.dataCount) {
1138      // 剩余数据不足20个时,使用最后一个分组处理
1139      if (this.dataCount - count < 20) {
1140        this.lastSection.itemsCount = this.dataCount - count;
1141        sectionOptions.push(this.lastSection);
1142        break;
1143      }
1144
1145      // 交替使用单列和双列布局
1146      if (oneOrTwo++ % 2 == 0) {
1147        sectionOptions.push(this.oneColumnSection);
1148        count += this.oneColumnSection.itemsCount;
1149      } else {
1150        sectionOptions.push(this.twoColumnSection);
1151        count += this.twoColumnSection.itemsCount;
1152      }
1153    }
1154
1155    // 将配置好的分组添加到WaterFlow中
1156    this.sections.splice(0, 0, sectionOptions);
1157  }
1158
1159  build() {
1160    Column({ space: 2 }) {
1161      Row() {
1162        Button('splice')
1163          .height('5%')
1164          .onClick(() => {
1165            // 重要:必须保证LazyForEach中数据数量和新分组itemsCount累计总数保持一致
1166            let totalCount: number = this.dataSource.totalCount();
1167            let newSection: SectionOptions = {
1168              itemsCount: totalCount,
1169              crossCount: 2,
1170              onGetItemMainSizeByIndex: (index: number) => {
1171                return this.itemHeightArray[index % 100];
1172              }
1173            };
1174            let oldLength: number = this.sections.length();
1175            this.sections.splice(0, oldLength, [newSection]);  // 替换所有分组
1176          })
1177          .margin({ top: 10, left: 20 })
1178
1179        Button('update')
1180          .height('5%')
1181          .onClick(() => {
1182            // 在第一个分组中增加4个FlowItem
1183            // 重要:必须保证数据源和分组itemsCount同步更新
1184            const sections: Array<SectionOptions> = this.sections.values();
1185            let newSection: SectionOptions = sections[0];
1186
1187            // 先在数据源中添加4个新数据
1188            this.dataSource.addItem(this.oneColumnSection.itemsCount);
1189            this.dataSource.addItem(this.oneColumnSection.itemsCount + 1);
1190            this.dataSource.addItem(this.oneColumnSection.itemsCount + 2);
1191            this.dataSource.addItem(this.oneColumnSection.itemsCount + 3);
1192
1193            // 然后更新分组的itemsCount
1194            newSection.itemsCount += 4;
1195            const result: boolean = this.sections.update(0, newSection);
1196            console.info('update:' + result);
1197          })
1198          .margin({ top: 10, left: 20 })
1199
1200        Button('delete')
1201          .height('5%')
1202          .onClick(() => {
1203            // 在第一个分组中减少4个FlowItem
1204            // 重要:必须保证数据源和分组itemsCount同步更新
1205            const sections: Array<SectionOptions> = this.sections.values();
1206            let newSection: SectionOptions = sections[0];
1207
1208            // 检查是否有足够的item可以删除
1209            if (newSection.itemsCount < 4) {
1210              return;
1211            }
1212
1213            // 先从数据源中删除4条数据
1214            this.dataSource.deleteItem(this.oneColumnSection.itemsCount - 1);
1215            this.dataSource.deleteItem(this.oneColumnSection.itemsCount - 2);
1216            this.dataSource.deleteItem(this.oneColumnSection.itemsCount - 3);
1217            this.dataSource.deleteItem(this.oneColumnSection.itemsCount - 4);
1218
1219            // 更新分组的itemsCount
1220            newSection.itemsCount -= 4;
1221            this.sections.update(0, newSection);
1222          })
1223          .margin({ top: 10, left: 20 })
1224
1225        Button('values')
1226          .height('5%')
1227          .onClick(() => {
1228            const sections: Array<SectionOptions> = this.sections.values();
1229            for (const value of sections) {
1230              console.log(JSON.stringify(value));
1231            }
1232            console.info('count:' + this.sections.length());
1233          })
1234          .margin({ top: 10, left: 20 })
1235      }.margin({ bottom: 20 })
1236
1237      WaterFlow({ scroller: this.scroller, sections: this.sections }) {
1238        LazyForEach(this.dataSource, (item: number) => {
1239          FlowItem() {
1240            // 使用可复用组件,提升性能
1241            ReusableFlowItem({ item: item })
1242          }
1243          .width('100%')
1244          // 注意:同时设置onGetItemMainSizeByIndex和height属性时,
1245          // 主轴大小以onGetItemMainSizeByIndex返回结果为准
1246          .height(this.itemHeightArray[item % 100])
1247          .backgroundColor(this.colors[item % this.colors.length])
1248        }, (item: string) => item)
1249      }
1250      .columnsTemplate('1fr 1fr')
1251      .columnsGap(10)
1252      .rowsGap(5)
1253      .backgroundColor(0xFAEEE0)
1254      .width('100%')
1255      .height('100%')
1256      .layoutWeight(1)
1257      .onScrollIndex((first: number, last: number) => {
1258        // 滚动监听:即将触底时提前加载更多数据
1259        if (last + 20 >= this.dataSource.totalCount()) {
1260          // 添加100个新数据到数据源
1261          for (let i = 0; i < 100; i++) {
1262            this.dataSource.addLastItem();
1263          }
1264
1265          // 重要:更新数据源后必须同步更新sections
1266          // 修改最后一个section的FlowItem数量
1267          const sections: Array<SectionOptions> = this.sections.values();
1268          let newSection: SectionOptions = sections[this.sections.length() - 1];
1269          newSection.itemsCount += 100;
1270          this.sections.update(-1, newSection);  // -1表示最后一个分组
1271        }
1272      })
1273    }
1274  }
1275}
1276```
1277
1278![waterflowSections.png](figures/waterflowSections.png)
1279
1280### 示例4(双指缩放改变列数)
1281该示例通过[priorityGesture](ts-gesture-settings.md)和[PinchGesture](ts-basic-gestures-pinchgesture.md)实现了双指缩放改变列数效果。
1282
1283WaterFlowDataSource说明及完整代码参考[示例1使用基本瀑布流](#示例1使用基本瀑布流)。
1284
1285<!--code_no_check-->
1286```ts
1287// Index.ets
1288import { WaterFlowDataSource } from './WaterFlowDataSource';
1289import { image } from '@kit.ImageKit';
1290
1291// 可复用组件:优化性能,减少组件创建销毁开销
1292@Reusable
1293@Component
1294struct ReusableFlowItem {
1295  @State item: number = 0;
1296
1297  // 从复用缓存中加入到组件树之前调用,可在此处更新组件的状态变量以展示正确的内容
1298  aboutToReuse(params: Record<string, number>) {
1299    this.item = params.item;
1300  }
1301
1302  build() {
1303    Column() {
1304      Text("N" + this.item).fontSize(12).height('16')
1305      // 注意:需要确保对应的jpg文件存在才会正常显示
1306      Image('res/waterFlow(' + this.item % 5 + ').jpg')
1307        .objectFit(ImageFit.Fill)
1308        .width('100%')
1309        .layoutWeight(1)
1310    }
1311  }
1312}
1313
1314@Entry
1315@Component
1316struct WaterFlowDemo {
1317  minSize: number = 80;
1318  maxSize: number = 180;
1319  colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F];
1320  dataSource: WaterFlowDataSource = new WaterFlowDataSource();
1321  private itemWidthArray: number[] = [];
1322  private itemHeightArray: number[] = [];
1323  @State columns: number = 2;
1324  @State waterFlowScale: number = 1;
1325  @State imageScale: number = 1;
1326  @State waterFlowOpacity: number = 1;
1327  @State waterFlowSnapshot: image.PixelMap | undefined = undefined;
1328  private columnChanged: boolean = false;
1329  private oldColumn: number = this.columns;
1330  private pinchTime: number = 0;
1331
1332  // 计算FlowItem宽/高
1333  getSize() {
1334    let ret = Math.floor(Math.random() * this.maxSize);
1335    return (ret > this.minSize ? ret : this.minSize);
1336  }
1337
1338  // 设置FlowItem的宽/高数组
1339  setItemSizeArray() {
1340    for (let i = 0; i < 100; i++) {
1341      this.itemWidthArray.push(this.getSize());
1342      this.itemHeightArray.push(this.getSize());
1343    }
1344  }
1345
1346  // 组件生命周期:初始化数据和恢复上次的列数设置
1347  aboutToAppear() {
1348    // 读取上次最后切换到到列数
1349    let lastCount = AppStorage.get<number>('columnsCount');
1350    if (typeof lastCount != 'undefined') {
1351      this.columns = lastCount;
1352    }
1353    this.setItemSizeArray();
1354  }
1355
1356  // 根据缩放阈值改变列数,触发WaterFlow重新布局
1357  changeColumns(scale: number) {
1358    if (scale > (this.columns / (this.columns - 0.5)) && this.columns > 1) {
1359      this.columns--;
1360      this.columnChanged = true;
1361    } else if (scale < 1 && this.columns < 4) {
1362      this.columns++;
1363      this.columnChanged = true;
1364    }
1365  }
1366
1367  build() {
1368    Column({ space: 2 }) {
1369      Row() {
1370        Text('双指缩放改变列数')
1371          .height('5%')
1372          .margin({ top: 10, left: 20 })
1373      }
1374
1375      Stack() {
1376        // 用于展示缩放前的WaterFlow截图
1377        Image(this.waterFlowSnapshot)
1378          .width('100%')
1379          .height('100%')
1380          .scale({
1381            x: this.imageScale,
1382            y: this.imageScale,
1383            centerX: 0,
1384            centerY: 0
1385          })
1386
1387        WaterFlow() {
1388          LazyForEach(this.dataSource, (item: number) => {
1389            FlowItem() {
1390              // 使用可复用组件,提升性能
1391              ReusableFlowItem({ item: item })
1392            }
1393            .width('100%')
1394            .aspectRatio(this.itemHeightArray[item % 100] / this.itemWidthArray[item%100])
1395            .backgroundColor(this.colors[item % this.colors.length])
1396          }, (item: string) => item)
1397        }
1398        .id('waterflow') // 设置id用于截图
1399        .columnsTemplate('1fr '.repeat(this.columns))  // 动态生成列模板,如:'1fr 1fr 1fr'表示3列等宽
1400        .backgroundColor(0xFAEEE0)
1401        .width('100%')
1402        .height('100%')
1403        .layoutWeight(1)
1404        .opacity(this.waterFlowOpacity)
1405        .scale({
1406          x: this.waterFlowScale,
1407          y: this.waterFlowScale,
1408          centerX: 0,
1409          centerY: 0
1410        })
1411        .priorityGesture(
1412          PinchGesture()
1413            .onActionStart((event: GestureEvent) => {
1414              // 双指捏合手势识别成功时截图
1415              this.pinchTime = event.timestamp;
1416              this.columnChanged = false;
1417              this.oldColumn = this.columns;
1418              this.getUIContext().getComponentSnapshot().get('waterflow', (error: Error, pixmap: image.PixelMap) => {
1419                if (error) {
1420                  console.info('error:' + JSON.stringify(error));
1421                  return;
1422                }
1423                this.waterFlowSnapshot = pixmap;
1424              })
1425            })
1426            .onActionUpdate((event: GestureEvent) => {
1427              // 手势更新:处理缩放逻辑和视觉效果
1428              // 边界限制:防止超出列数范围时继续缩放
1429              if ((this.oldColumn === 1 && event.scale > 1) || (this.oldColumn === 4 && event.scale < 1)) {
1430                return;
1431              }
1432
1433              // 节流处理:避免过于频繁的更新,提升性能
1434              if (event.timestamp - this.pinchTime < 10000000) {
1435                return;
1436              }
1437              this.pinchTime = event.timestamp;
1438
1439              this.waterFlowScale = event.scale;
1440              this.imageScale = event.scale;
1441              // 根据缩放比例设置WaterFlow透明度
1442              this.waterFlowOpacity = (this.waterFlowScale > 1) ? (this.waterFlowScale - 1) : (1 - this.waterFlowScale);
1443              this.waterFlowOpacity *= 3;
1444              if (!this.columnChanged) {
1445                this.changeColumns(event.scale);
1446              }
1447
1448              // 列数改变后的缩放比例调整:避免出现空白区域
1449              if (this.columnChanged) {
1450                this.waterFlowScale = this.imageScale * this.columns / this.oldColumn;
1451
1452                // 限制缩放范围,确保视觉效果自然
1453                if (event.scale < 1) {
1454                  this.waterFlowScale = this.waterFlowScale > 1 ? this.waterFlowScale : 1;
1455                } else {
1456                  this.waterFlowScale = this.waterFlowScale < 1 ? this.waterFlowScale : 1;
1457                }
1458              }
1459            })
1460            .onActionEnd((event: GestureEvent) => {
1461              // 手势结束:执行归位动画并保存状态
1462              // 执行归位动画:平滑过渡到正常状态
1463              this.getUIContext()?.animateTo({ duration: 300 }, () => {
1464                this.waterFlowScale = 1;
1465                this.waterFlowOpacity = 1;
1466              })
1467
1468              // 持久化保存当前列数:下次启动时恢复
1469              AppStorage.setOrCreate<number>('columnsCount', this.columns);
1470            })
1471        )
1472      }
1473    }
1474  }
1475}
1476```
1477
1478![pinch](figures/waterflow-pinch.gif)
1479
1480### 示例5(设置边缘渐隐效果)
1481该示例通过[fadingEdge](ts-container-scrollable-common.md#fadingedge14)实现了WaterFlow组件开启边缘渐隐效果,并通过fadingEdgeLength参数设置边缘渐隐长度。
1482
1483WaterFlowDataSource说明及完整代码参考[示例1使用基本瀑布流](#示例1使用基本瀑布流)。
1484
1485<!--code_no_check-->
1486```ts
1487// Index.ets
1488import { LengthMetrics } from '@kit.ArkUI';
1489import { WaterFlowDataSource } from './WaterFlowDataSource';
1490
1491@Entry
1492@Component
1493struct WaterFlowDemo {
1494  @State minSize: number = 80;
1495  @State maxSize: number = 180;
1496  @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F];
1497  dataSource: WaterFlowDataSource = new WaterFlowDataSource();
1498  scroller: Scroller = new Scroller();
1499  private itemWidthArray: number[] = [];
1500  private itemHeightArray: number[] = [];
1501
1502  // 计算FlowItem宽/高
1503  getSize() {
1504    let ret = Math.floor(Math.random() * this.maxSize);
1505    return (ret > this.minSize ? ret : this.minSize);
1506  }
1507
1508  // 设置FlowItem宽/高数组
1509  setItemSizeArray() {
1510    for (let i = 0; i < 100; i++) {
1511      this.itemWidthArray.push(this.getSize());
1512      this.itemHeightArray.push(this.getSize());
1513    }
1514  }
1515
1516  // 组件生命周期:在组件即将出现时初始化尺寸数组
1517  aboutToAppear() {
1518    this.setItemSizeArray();
1519  }
1520
1521  build() {
1522    Column({ space: 2 }) {
1523      WaterFlow({ scroller: this.scroller }) {
1524        LazyForEach(this.dataSource, (item: number) => {
1525          FlowItem() {
1526            Column() {
1527              Text("N" + item).fontSize(12).height('16')
1528            }
1529          }
1530          .width('100%')
1531          .height(this.itemHeightArray[item % 100])
1532          .backgroundColor(this.colors[item % 5])
1533        }, (item: string) => item)
1534      }
1535      // auto-fill自动计算列数:根据容器宽度自动计算能放下多少个80px宽的列
1536      .columnsTemplate('repeat(auto-fill,80)')
1537      .columnsGap(10)
1538      .rowsGap(5)
1539      .height('90%')
1540      .scrollBar(BarState.On)
1541      // 边缘渐隐效果:在滚动边缘创建渐隐过渡效果
1542      // true:启用渐隐效果
1543      // fadingEdgeLength: LengthMetrics.vp(80):渐隐区域长度为80vp
1544      // 效果:在瀑布流顶部和底部边缘会有80vp的渐隐过渡区域
1545      .fadingEdge(true, { fadingEdgeLength: LengthMetrics.vp(80) })
1546    }
1547  }
1548}
1549```
1550
1551![fadingEdge_waterFlow](figures/fadingEdge_waterFlow.gif)
1552
1553### 示例6(单边边缘效果)
1554
1555该示例通过edgeEffect接口,实现了WaterFlow组件设置单边边缘效果。
1556
1557WaterFlowDataSource说明及完整代码参考[示例1使用基本瀑布流](#示例1使用基本瀑布流)。
1558
1559<!--code_no_check-->
1560```ts
1561// Index.ets
1562import { WaterFlowDataSource } from './WaterFlowDataSource';
1563
1564@Entry
1565@Component
1566struct WaterFlowDemo {
1567  @State minSize: number = 80;
1568  @State maxSize: number = 180;
1569  @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F];
1570  dataSource: WaterFlowDataSource = new WaterFlowDataSource();
1571  scroller: Scroller = new Scroller();
1572  private itemWidthArray: number[] = [];
1573  private itemHeightArray: number[] = [];
1574
1575  // 计算FlowItem宽/高
1576  getSize() {
1577    let ret = Math.floor(Math.random() * this.maxSize);
1578    return (ret > this.minSize ? ret : this.minSize);
1579  }
1580
1581  // 设置FlowItem宽/高数组
1582  setItemSizeArray() {
1583    for (let i = 0; i < 100; i++) {
1584      this.itemWidthArray.push(this.getSize());
1585      this.itemHeightArray.push(this.getSize());
1586    }
1587  }
1588
1589  // 组件生命周期:在组件即将出现时初始化尺寸数组
1590  aboutToAppear() {
1591    this.setItemSizeArray();
1592  }
1593
1594  build() {
1595    Column({ space: 2 }) {
1596      WaterFlow({ scroller: this.scroller }) {
1597        LazyForEach(this.dataSource, (item: number) => {
1598          FlowItem() {
1599            Column() {
1600              Text("N" + item).fontSize(12).height('16')
1601            }
1602          }
1603          .width('100%')
1604          .height(this.itemHeightArray[item % 100])
1605          .backgroundColor(this.colors[item % 5])
1606        }, (item: number) => item.toString())
1607      }
1608      // auto-fill自动计算列数:根据容器宽度自动计算能放下多少个80px宽的列
1609      .columnsTemplate('repeat(auto-fill,80)')
1610      .columnsGap(10)
1611      .rowsGap(5)
1612      .height('90%')
1613      // 单边边缘效果:设置弹簧效果,仅在顶部生效
1614      // EdgeEffect.Spring:弹簧回弹效果,滑动到边界时会有弹性回弹
1615      // alwaysEnabled: true:始终启用边缘效果,即使内容不足以滚动
1616      // effectEdge: EffectEdge.START:仅在起始边缘(顶部)生效
1617      // 效果:只有向上滑动到顶部时才会有弹簧回弹效果,向下滑动到底部不会有效果
1618      .edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true, effectEdge: EffectEdge.START })
1619
1620    }
1621  }
1622}
1623```
1624
1625![edgeEffect_waterFlow](figures/edgeEffect_waterflow.gif)
1626
1627### 示例7(WaterFlow组件设置和改变尾部组件)
1628
1629该示例通过footerContent接口,实现了WaterFlow组件设置尾部组件。通过ComponentContent的update函数更新尾部组件。
1630
1631WaterFlowDataSource说明及完整代码参考[示例1使用基本瀑布流](#示例1使用基本瀑布流)。
1632
1633<!--code_no_check-->
1634```ts
1635// Index.ets
1636import { ComponentContent, UIContext } from "@kit.ArkUI";
1637import { WaterFlowDataSource } from './WaterFlowDataSource';
1638
1639class Params {
1640  text: string = "";
1641
1642  constructor(text: string) {
1643    this.text = text;
1644  }
1645}
1646
1647// Builder函数:构建尾部组件的UI结构
1648@Builder
1649function buildText(params: Params) {
1650  Column() {
1651    Text(params.text)
1652      .fontSize(20)
1653      .fontWeight(FontWeight.Bold)
1654      .margin(20)
1655  }
1656}
1657
1658@Entry
1659@Component
1660struct Index {
1661  @State message1: string = "已经到底了";
1662  @State message2: string = "加载更多";
1663  @State colors: number[] = [0xD5D5D5, 0x7F7F7F, 0xF7F7F7];
1664  @State minSize: number = 80;
1665  @State maxSize: number = 180;
1666
1667  // UI上下文:用于创建ComponentContent
1668  context: UIContext = this.getUIContext();
1669
1670  // 动态尾部组件:使用ComponentContent创建可更新的尾部组件
1671  // ComponentContent<Params>:泛型指定参数类型
1672  // wrapBuilder<[Params]>(buildText):包装Builder函数
1673  // new Params(this.message1):初始参数,显示"已经到底了"
1674  footerContent: ComponentContent<Params> = new ComponentContent<Params>(
1675    this.context,
1676    wrapBuilder<[Params]>(buildText),
1677    new Params(this.message1)
1678  );
1679
1680  dataSource: WaterFlowDataSource = new WaterFlowDataSource();
1681  private itemWidthArray: number[] = [];
1682  private itemHeightArray: number[] = [];
1683
1684  // 计算FlowItem宽/高
1685  getSize() {
1686    let ret = Math.floor(Math.random() * this.maxSize);
1687    return (ret > this.minSize ? ret : this.minSize);
1688  }
1689
1690  // 设置FlowItem宽/高数组
1691  setItemSizeArray() {
1692    for (let i = 0; i < 100; i++) {
1693      this.itemWidthArray.push(this.getSize());
1694      this.itemHeightArray.push(this.getSize());
1695    }
1696  }
1697
1698  // 组件生命周期:在组件即将出现时初始化尺寸数组
1699  aboutToAppear() {
1700    this.setItemSizeArray();
1701  }
1702
1703  build() {
1704    Row() {
1705      Column() {
1706        Button("更新footer").width('90%').margin(20)
1707          .onClick((event?: ClickEvent) => {
1708            // 调用ComponentContent的update方法更新尾部组件
1709            // 传入新的Params对象,文本内容从"已经到底了"变为"加载更多"
1710            this.footerContent.update(new Params(this.message2));
1711          })
1712        WaterFlow({ footerContent: this.footerContent }) {
1713          LazyForEach(this.dataSource, (item: number) => {
1714            FlowItem() {
1715              Column() {
1716                Text("N" + item).fontSize(12).height('16')
1717              }
1718              .width('100%')
1719              .height(this.itemHeightArray[item % 100])
1720              .backgroundColor(this.colors[item % 3])
1721              .justifyContent(FlexAlign.Center)
1722              .alignItems(HorizontalAlign.Center)
1723            }
1724          }, (item: number) => item.toString())
1725        }
1726        .columnsTemplate('1fr')
1727        .height("90%")
1728      }
1729      .width('100%')
1730      .height('100%')
1731    }
1732    .height('100%')
1733  }
1734}
1735```
1736
1737![waterFlow_footerContent](figures/waterFlow_footerContent.gif)
1738
1739### 示例8(WaterFlow组件实现下拉刷新)
1740
1741该示例通过Refresh组件和WaterFlow组件,实现了下拉刷新瀑布流组件数据源。
1742
1743WaterFlowDataSource说明及完整代码参考[示例1使用基本瀑布流](#示例1使用基本瀑布流)。
1744
1745<!--code_no_check-->
1746```ts
1747// Index.ets
1748import { WaterFlowDataSource } from './WaterFlowDataSource';
1749
1750@Entry
1751@Component
1752struct WaterFlowDemo {
1753  @State minSize: number = 80;
1754  @State maxSize: number = 180;
1755  @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F];
1756  @State isRefreshing: boolean = false;
1757  dataSource: WaterFlowDataSource = new WaterFlowDataSource();
1758  scroller: Scroller = new Scroller();
1759  private itemWidthArray: number[] = [];
1760  private itemHeightArray: number[] = [];
1761
1762  // 计算FlowItem宽/高
1763  getSize() {
1764    let ret = Math.floor(Math.random() * this.maxSize);
1765    return (ret > this.minSize ? ret : this.minSize);
1766  }
1767
1768  // 设置FlowItem宽/高数组
1769  setItemSizeArray() {
1770    for (let i = 0; i < 100; i++) {
1771      this.itemWidthArray.push(this.getSize());
1772      this.itemHeightArray.push(this.getSize());
1773    }
1774  }
1775
1776  // 组件生命周期:在组件即将出现时初始化尺寸数组
1777  aboutToAppear() {
1778    this.setItemSizeArray();
1779  }
1780
1781  build() {
1782    Column({ space: 2 }) {
1783      // refreshing: $$this.isRefreshing:双向绑定刷新状态
1784      Refresh({ refreshing: $$this.isRefreshing }) {
1785        WaterFlow({ scroller: this.scroller }) {
1786          LazyForEach(this.dataSource, (item: number) => {
1787            FlowItem() {
1788              Column() {
1789                Text('N' + item).fontSize(12).height('16')
1790              }
1791            }
1792            .width('100%')
1793            .height(this.itemHeightArray[item % 100])
1794            .backgroundColor(this.colors[item % this.colors.length])
1795          }, (item: number) => item.toString())
1796        }
1797        // auto-fill自动计算列数:根据容器宽度自动计算能放下多少个80px宽的列
1798        .columnsTemplate('repeat(auto-fill,80)')
1799        .columnsGap(10)
1800        .rowsGap(5)
1801        .height('90%')
1802        // 边缘效果:弹簧回弹效果
1803        .edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true })
1804        .onReachEnd(() => {
1805          // 触底加载更多数据:滚动到底部时触发
1806          setTimeout(() => {
1807            this.dataSource.addNewItems(100);
1808          }, 1000)
1809        })
1810      }
1811      .onStateChange((refreshStatus: RefreshStatus) => {
1812        // 刷新状态变化监听:处理不同的刷新状态
1813        if (refreshStatus === RefreshStatus.Done) {
1814          // 刷新完成时:调用数据源的刷新方法,更新所有数据
1815          this.dataSource.refreshItems();
1816        }
1817      })
1818      .onRefreshing(() => {
1819        // 正在刷新时的回调:模拟刷新过程
1820        setTimeout(() => {
1821          this.isRefreshing = false;
1822        }, 1000)
1823      })
1824    }
1825  }
1826}
1827```
1828
1829![waterFlow_refresh](figures/waterFlow_refresh.gif)