1# WaterFlow
2
3
4The **WaterFlow** component is a water flow container that consists of cells formed by rows and columns and arranges items of different sizes from top to bottom according to the preset rules.
5
6
7> **NOTE**
8>
9> This component is supported since API version 9. Updates will be marked with a superscript to indicate their earliest API version.
10
11
12## Child Components
13
14
15Only the [FlowItem](ts-container-flowitem.md) child component is allowed, with support for [if/else](../../../quick-start/arkts-rendering-control-ifelse.md), [ForEach](../../../quick-start/arkts-rendering-control-foreach.md), [LazyForEach](../../../quick-start/arkts-rendering-control-lazyforeach.md), and [Repeat](../../../quick-start/arkts-new-rendering-control-repeat.md) rendering control.
16
17>  **NOTE**
18>
19>  When its **visibility** attribute is set to **None**, a **FlowItem** is not displayed in the container, but its **columnsGap**, **rowsGap**, and **margin** settings are still effective.
20
21## APIs
22
23WaterFlow(options?:  WaterFlowOptions)
24
25**Atomic service API**: This API can be used in atomic services since API version 11.
26
27**System capability**: SystemCapability.ArkUI.ArkUI.Full
28
29**Parameters**
30
31| Name| Type| Mandatory| Description|
32| -------- | -------- | -------- | -------- |
33| options |  [WaterFlowOptions](#waterflowoptions)| No| Parameters of the **WaterFlow** component.|
34
35
36## WaterFlowOptions
37
38**System capability**: SystemCapability.ArkUI.ArkUI.Full
39
40| Name    | Type                                       | Mandatory| Description                                    |
41| ---------- | ----------------------------------------------- | ------ | -------------------------------------------- |
42| footer |  [CustomBuilder](ts-types.md#custombuilder8) | No  | Footer of the **WaterFlow** component.<br>**Atomic service API**: This API can be used in atomic services since API version 11.|
43| scroller | [Scroller](ts-container-scroll.md#scroller) | No  | Controller of the scrollable component, bound to the scrollable component.<br>**NOTE**<br>The same scroller cannot be bound to other scrollable components, such as [List](ts-container-list.md), [Grid](ts-container-grid.md), or [Scroll](ts-container-scroll.md).<br>**Atomic service API**: This API can be used in atomic services since API version 11.|
44| sections<sup>12+</sup> |  [WaterFlowSections](#waterflowsections12) | No  | Water flow item sections. Different sections can have different numbers of columns.<br>**NOTE**<br>1. When **sections** is used, the **columnsTemplate** and **rowsTemplate** attributes are ignored.<br>2. When **sections** is used, the footer cannot be set separately. The last section can function as the footer.<br>**Atomic service API**: This API can be used in atomic services since API version 12. |
45| layoutMode<sup>12+</sup> |[WaterFlowLayoutMode](#waterflowlayoutmode12)| No| Layout mode of the **WaterFlow** component.<br>**NOTE**<br>Default value: [ALWAYS_TOP_DOWN](#waterflowlayoutmode12)<br>**Atomic service API**: This API can be used in atomic services since API version 12.
46
47
48## WaterFlowSections<sup>12+</sup>
49
50Describes the water flow item sections.
51
52### constructor
53
54constructor()
55
56A constructor used to create a **WaterFlowSections** object.
57
58**Atomic service API**: This API can be used in atomic services since API version 12.
59
60**System capability**: SystemCapability.ArkUI.ArkUI.Full
61
62### splice<sup>12+</sup>
63
64splice(start: number, deleteCount?: number, sections?: Array\<SectionOptions\>): boolean
65
66Changes sections by removing or replacing an existing section and/or adding a section.
67
68**Atomic service API**: This API can be used in atomic services since API version 12.
69
70**System capability**: SystemCapability.ArkUI.ArkUI.Full
71
72**Parameters**
73
74| Name  | Type                           | Mandatory  | Description                  |
75| ---- | ----------------------------- | ---- | -------------------- |
76| start | number | Yes   | Zero-based index at which the changing starts. The value is converted to an integer.<br>**NOTE**<br>1. A negative index counts back from the end of the section list. If -**WaterFlowSections.length()** <= **start** < **0**, **start** + **array.length** is used.<br>2. If **start** < -**WaterFlowSections.length()**, **0** is used.<br>3. If **start** >= **WaterFlowSections.length()**, a new section is added at the end.|
77| deleteCount | number | No   | Number of sections to be deleted from the position specified by **start**.<br>**NOTE**<br>1. If **deleteCount** is omitted, or if its value is greater than or equal to the number of sections from the position specified by **start** to the end of the **WaterFlowSections**, then all sections from the position specified by **start** to the end of the **WaterFlowSections** will be deleted.<br>2. If **deleteCount** is **0** or a negative number, no sections are deleted.|
78| sections | Array<[SectionOptions](#sectionoptions12)> | No   | Sections to add to the section list, beginning from the position specified by **start**. If no section is specified, **splice()** will only delete sections from the **WaterFlow** component.|
79
80**Return value**
81
82| Type                                                        | Description                                                        |
83| ------------------------------------------------------------ | ------------------------------------------------------------ |
84| boolean | Whether the changing is successful. If the value of **itemsCount** in any section to add is not a positive integer, **false** is returned.|
85
86
87### push<sup>12+</sup>
88
89push(section: SectionOptions): boolean
90
91Adds the specified sections to the end of the **WaterFlow** component.
92
93**Atomic service API**: This API can be used in atomic services since API version 12.
94
95**System capability**: SystemCapability.ArkUI.ArkUI.Full
96
97**Parameters**
98
99| Name  | Type                           | Mandatory  | Description                  |
100| ---- | ----------------------------- | ---- | -------------------- |
101| section | [SectionOptions](#sectionoptions12) | Yes   | Sections to add to the end of the **WaterFlow** component.|
102
103**Return value**
104
105| Type                                                        | Description                                                        |
106| ------------------------------------------------------------ | ------------------------------------------------------------ |
107| boolean | Whether the adding is successful. If the value of **itemsCount** in any section to add is not a positive integer, **false** is returned.|
108
109### update<sup>12+</sup>
110
111update(sectionIndex: number, section: SectionOptions): boolean
112
113Updates the configuration of a specified water flow item section.
114
115**Atomic service API**: This API can be used in atomic services since API version 12.
116
117**System capability**: SystemCapability.ArkUI.ArkUI.Full
118
119**Parameters**
120
121| Name  | Type                           | Mandatory  | Description                  |
122| ---- | ----------------------------- | ---- | -------------------- |
123| sectionIndex | number | Yes   | Zero-based index of the water flow item section to update. The value is converted to an integer.<br>**NOTE**<br>1. A negative index counts back from the end of the section list. If -**WaterFlowSections.length()** <= **sectionIndex** < **0**, **sectionIndex** + **array.length** is used.<br>2. If **sectionIndex** < -**WaterFlowSections.length()**, **0** is used.<br>3. If **sectionIndex** >= **WaterFlowSections.length()**, a new section is added at the end.|
124| section | [SectionOptions](#sectionoptions12) | Yes   | New section configuration.|
125
126**Return value**
127
128| Type                                                        | Description                                                        |
129| ------------------------------------------------------------ | ------------------------------------------------------------ |
130| boolean | Whether the update is successful. If the value of **itemsCount** in any section to add is not a positive integer, **false** is returned.|
131
132### values<sup>12+</sup>
133
134values(): Array\<SectionOptions\>
135
136Obtains the configuration of all sections in the **WaterFlow** component.
137
138**Atomic service API**: This API can be used in atomic services since API version 12.
139
140**System capability**: SystemCapability.ArkUI.ArkUI.Full
141
142**Return value**
143
144| Type                                                        | Description                                                        |
145| ------------------------------------------------------------ | ------------------------------------------------------------ |
146| Array<[SectionOptions](#sectionoptions12)> | Configuration of all sections in the **WaterFlow** component.|
147
148### length<sup>12+</sup>
149
150length(): number
151
152Obtains the number of sections in the **WaterFlow** component.
153
154**Atomic service API**: This API can be used in atomic services since API version 12.
155
156**System capability**: SystemCapability.ArkUI.ArkUI.Full
157
158**Return value**
159
160| Type                                                        | Description                                                        |
161| ------------------------------------------------------------ | ------------------------------------------------------------ |
162| number | Number of sections in the **WaterFlow** component.|
163
164## SectionOptions<sup>12+</sup>
165
166Describes the configuration of the water flow item section.
167
168**Atomic service API**: This API can be used in atomic services since API version 12.
169
170**System capability**: SystemCapability.ArkUI.ArkUI.Full
171
172**Parameters**
173
174| Name| Type| Mandatory| Description|
175|------|-----|-----|-----|
176| itemsCount | number | Yes| Number of water flow items in the section. The value must be a positive integer.|
177| crossCount | number | No| Number of columns (in vertical layout) or rows (in horizontal layout).<br>Default value: **1**|
178| columnsGap | [Dimension](ts-types.md#dimension10) | No| Gap between columns. If this parameter is not set, the value of **columnsGap** for the water flow is used. If this parameter is set to an invalid value, 0 vp is used.|
179| rowsGap | [Dimension](ts-types.md#dimension10) | No| Gap between rows. If this parameter is not set, the value of **rowsGap** for the water flow is used. If this parameter is set to an invalid value, 0 vp is used.|
180| margin | [Margin](ts-types.md#margin) \| [Dimension](ts-types.md#dimension10) | No| Padding of the section. A value of the Length type specifies the margin for all the four sides.<br>Default value: **0**<br>Unit: vp<br>When **margin** is set to a percentage, the width of the **WaterFlow** component is used as the base value for the top, bottom, left, and right margins.|
181| onGetItemMainSizeByIndex | [GetItemMainSizeByIndex](#getitemmainsizebyindex12) | No| Callback used to obtain the main axis size, in vp, of the water flow item at a specified index during the layout process of the **WaterFlow** component. For a vertical **WaterFlow** component, this size refers to the height, and for a horizontal **WaterFlow** component, it refers to the width.<br>**NOTE**<br>1. When both **onGetItemMainSizeByIndex** and the width or height attribute of the water flow item are used, the main axis size is determined by the return value of **onGetItemMainSizeByIndex**, which will override the main axis length of water flow item.<br>2. Using **onGetItemMainSizeByIndex** can improve the efficiency of jumping to a specific position or index in the **WaterFlow** component. Avoid mixing the use of **onGetItemMainSizeByIndex** with sections that do not have it set, as this can cause layout exceptions.<br>3. If **onGetItemMainSizeByIndex** returns a negative number, the height of the water flow item is 0.|
182
183
184## GetItemMainSizeByIndex<sup>12+</sup>
185
186type GetItemMainSizeByIndex = (index: number) => number
187
188Obtains the main axis size of a specified water flow item based on its index.
189
190**Atomic service API**: This API can be used in atomic services since API version 12.
191
192**System capability**: SystemCapability.ArkUI.ArkUI.Full
193
194**Parameters**
195
196| Name  | Type                           | Mandatory  | Description                  |
197| ---- | ----------------------------- | ---- | -------------------- |
198| index | number | Yes   | Index of the target water flow item.|
199
200**Return value**
201
202| Type                                                        | Description                                                        |
203| ------------------------------------------------------------ | ------------------------------------------------------------ |
204| number | Main axis size, in vp, of the water flow item at the specified index, which is the height for a vertical **WaterFlow** component and the width for a horizontal **WaterFlow** component.|
205
206## WaterFlowLayoutMode<sup>12+</sup>
207
208**Atomic service API**: This API can be used in atomic services since API version 12.
209
210**System capability**: SystemCapability.ArkUI.ArkUI.Full
211
212| Name| Value| Description|
213| ------ | ------ | -------------------- |
214| ALWAYS_TOP_DOWN | 0 | Default layout mode from top to bottom. The water flow item within the viewport depends on the layout information of all water flow items above it. As such, in cases of redirection or changing the number of columns, it is necessary to calculate the layout information for all water flow items above.|
215| SLIDING_WINDOW | 1 | Sliding window layout mode. It only takes into account the layout information within the viewport and has no dependency on water flow items above the viewport. As such, in cases of redirection backward or switching the number of columns, only the layout of water flow items within the viewport needs to be handled. This mode is recommended for applications that frequently switch between different numbers of columns.<br>**NOTE**<br>1. During a non-animated redirection to a distant location, the layout of water flow items is based on the target position, either before or after it. If you then swipe back to the position prior to the redirection, the layout of the content may not be consistent with its previous state. This can lead to misalignment of the top nodes when you swipe back to the top after the redirection. To counteract this issue, in this layout mode, an automatic adjustment of the layout is made after reaching the top of the viewport to ensure that the top is aligned. In cases with multiple sections, the section at the top of the viewport will be adjusted once the swipe action is completed.<br> 2. The mode does not support the use of a scrollbar; even if a scrollbar is set, it will not be displayed.<br> 3. This mode does not support the [scrollTo](ts-container-scroll.md#scrollto) API of [scroller](#waterflowoptions).<br> 4. If a jump action (for example, by calling a non-animated [scrollToIndex](ts-container-scroll.md#scrolltoindex) or [scrollEdge](ts-container-scroll.md#scrolledge)) and an input offset (such as from a swipe gesture or a scrolling animation) are both initiated within the same frame, both will be executed.|
216
217
218## Attributes
219
220In addition to [universal attributes](ts-universal-attributes-size.md) and [scrollable component common attributes](ts-container-scrollable-common.md#attributes), the following attributes are also supported.
221
222### columnsTemplate
223
224columnsTemplate(value: string)
225
226Sets the number of columns in the layout. If this attribute is not set, one column is used by default.
227
228For example, **'1fr 1fr 2fr'** indicates three columns, with the first column taking up 1/4 of the parent component's full width, the second column 1/4, and the third column 2/4.
229
230You can use **columnsTemplate('repeat(auto-fill,track-size)')** to automatically calculate the number of columns based on the specified column width **track-size**. **repeat** and **auto-fill** are keywords. The units for **track-size** can be px, vp (default), %, or a valid number. For details, see Example 2.
231
232**Atomic service API**: This API can be used in atomic services since API version 11.
233
234**System capability**: SystemCapability.ArkUI.ArkUI.Full
235
236**Parameters**
237
238| Name| Type  | Mandatory| Description                                          |
239| ------ | ------ | ---- | ---------------------------------------------- |
240| value  | string | Yes  | Number of columns in the layout.<br>Default value: **'1fr'**|
241
242### rowsTemplate
243
244rowsTemplate(value: string)
245
246Sets the number of rows in the layout. If this attribute is not set, one row is used by default.
247
248For example, **'1fr 1fr 2fr'** indicates three rows, with the first row taking up 1/4 of the parent component's full height, the second row 1/4, and the third row 2/4.
249
250You can use **rowsTemplate('repeat(auto-fill,track-size)')** to automatically calculate the number of rows based on the specified row height **track-size**. **repeat** and **auto-fill** are keywords. The units for **track-size** can be px, vp (default), %, or a valid number.
251
252**Atomic service API**: This API can be used in atomic services since API version 11.
253
254**System capability**: SystemCapability.ArkUI.ArkUI.Full
255
256**Parameters**
257
258| Name| Type  | Mandatory| Description                                          |
259| ------ | ------ | ---- | ---------------------------------------------- |
260| value  | string | Yes  | Number of rows in the layout.<br>Default value: **'1fr'**|
261
262### itemConstraintSize
263
264itemConstraintSize(value: ConstraintSizeOptions)
265
266Sets the size constraints of the child components during layout.
267
268**Atomic service API**: This API can be used in atomic services since API version 11.
269
270**System capability**: SystemCapability.ArkUI.ArkUI.Full
271
272**Parameters**
273
274| Name| Type                                                      | Mandatory| Description      |
275| ------ | ---------------------------------------------------------- | ---- | ---------- |
276| value  | [ConstraintSizeOptions](ts-types.md#constraintsizeoptions) | Yes  | Size constraints of the child components during layout. If the value specified is less than 0, this parameter does not take effect.<br>**NOTE**<br>1. If both **itemConstraintSize** and the [constraintSize](ts-universal-attributes-size.md#constraintsize) attribute of the **FlowItem** are set, the **minWidth** (or **minHeight**) will be the larger of the two values, and the **maxWidth** (or **maxHeight**) will be the smaller of the two values. The resulting values will then be used as the **constraintSize** for the **FlowItem**. 2. When only **itemConstraintSize** is set, it effectively applies a uniform size constraint to all child components in the **WaterFlow**. 3. The **itemConstraintSize** attribute, once converted to the **constraintSize** attribute of the **FlowItem** through the two methods mentioned above, follows the same rules for taking effect as the universal attribute [constraintSize](./ts-universal-attributes-size.md#constraintsize).|
277
278### columnsGap
279
280columnsGap(value: Length)
281
282Sets the gap between columns.
283
284**Atomic service API**: This API can be used in atomic services since API version 11.
285
286**System capability**: SystemCapability.ArkUI.ArkUI.Full
287
288**Parameters**
289
290| Name| Type                        | Mandatory| Description                         |
291| ------ | ---------------------------- | ---- | ----------------------------- |
292| value  | [Length](ts-types.md#length) | Yes  | Gap between columns.<br>Default value: **0**|
293
294### rowsGap
295
296rowsGap(value: Length)
297
298Sets the gap between rows.
299
300**Atomic service API**: This API can be used in atomic services since API version 11.
301
302**System capability**: SystemCapability.ArkUI.ArkUI.Full
303
304**Parameters**
305
306| Name| Type                        | Mandatory| Description                         |
307| ------ | ---------------------------- | ---- | ----------------------------- |
308| value  | [Length](ts-types.md#length) | Yes  | Gap between rows.<br>Default value: **0**|
309
310### layoutDirection
311
312layoutDirection(value: FlexDirection)
313
314Sets the main axis direction of the layout.
315
316**Atomic service API**: This API can be used in atomic services since API version 11.
317
318**System capability**: SystemCapability.ArkUI.ArkUI.Full
319
320**Parameters**
321
322| Name| Type                                               | Mandatory| Description                                             |
323| ------ | --------------------------------------------------- | ---- | ------------------------------------------------- |
324| value  | [FlexDirection](ts-appendix-enums.md#flexdirection) | Yes  | Main axis direction of the layout.<br>Default value: **FlexDirection.Column**|
325
326The priority of **layoutDirection** is higher than that of **rowsTemplate** and **columnsTemplate**. Depending on the **layoutDirection** settings, there are three layout modes:
327
328- **layoutDirection** is set to **FlexDirection.Column** or **FlexDirection.ColumnReverse**
329
330  In this case, **columnsTemplate** is valid. If it is not set, the default value is used. For example, if **columnsTemplate** is set to **"1fr 1fr"** and **rowsTemplate** **"1fr 1fr 1fr"**, child components are arranged in vertical layout, with the cross axis equally divided into two columns.
331
332- **layoutDirection** set to **FlexDirection.Row** or **FlexDirection.RowReverse**
333
334  In this case, **rowsTemplate** is valid. If it is not set, the default value is used. For example, if **columnsTemplate** is set to **"1fr 1fr"** and **rowsTemplate** **"1fr 1fr 1fr"**, child components are arranged in horizontal layout, with the cross axis equally divided into three columns.
335
336- **layoutDirection** is not set
337
338  In this case, the default value of **layoutDirection** is used, which is **FlexDirection.Column**, and **columnsTemplate** is valid. For example, if **columnsTemplate** is set to **"1fr 1fr"** and **rowsTemplate** **"1fr 1fr 1fr"**, child components are arranged in vertical layout, with the cross axis equally divided into two columns.
339
340### enableScrollInteraction<sup>10+</sup>
341
342enableScrollInteraction(value: boolean)
343
344Sets whether to support scroll gestures. When this attribute is set to **false**, scrolling by finger or mouse is not supported, but the scrolling controller API is not affected.
345
346**Atomic service API**: This API can be used in atomic services since API version 11.
347
348**System capability**: SystemCapability.ArkUI.ArkUI.Full
349
350**Parameters**
351
352| Name| Type   | Mandatory| Description                               |
353| ------ | ------- | ---- | ----------------------------------- |
354| value  | boolean | Yes  | Whether to support scroll gestures.<br>Default value: **true**|
355
356### nestedScroll<sup>10+</sup>
357
358nestedScroll(value: NestedScrollOptions)
359
360Sets the nested scrolling mode in the forward and backward directions to implement scrolling linkage with the parent component.
361
362**Atomic service API**: This API can be used in atomic services since API version 11.
363
364**System capability**: SystemCapability.ArkUI.ArkUI.Full
365
366**Parameters**
367
368| Name| Type                                                        | Mandatory| Description          |
369| ------ | ------------------------------------------------------------ | ---- | -------------- |
370| value  | [NestedScrollOptions](ts-container-scrollable-common.md#nestedscrolloptions10) | Yes  | Nested scrolling options.|
371
372### friction<sup>10+</sup>
373
374friction(value: number | Resource)
375
376Sets the friction coefficient. It applies only to gestures in the scrolling area, and it affects only indirectly the scroll chaining during the inertial scrolling process. If this attribute is set to a value less than or equal to 0, the default value is used.
377
378**Atomic service API**: This API can be used in atomic services since API version 11.
379
380**System capability**: SystemCapability.ArkUI.ArkUI.Full
381
382**Parameters**
383
384| Name| Type                                                | Mandatory| Description                                                     |
385| ------ | ---------------------------------------------------- | ---- | --------------------------------------------------------- |
386| value  | number \| [Resource](ts-types.md#resource) | Yes  | Friction coefficient.<br>Default value: **0.9** for wearable devices and **0.6** for non-wearable devices.<br>Since API version 11, the default value for non-wearable devices is **0.7**.<br>Since API version 12, the default value for non-wearable devices is **0.75**.|
387
388### cachedCount<sup>11+</sup>
389
390cachedCount(value: number)
391
392Number of items to be cached. This attribute is effective only in [LazyForEach](../../../quick-start/arkts-rendering-control-lazyforeach.md). Items that exceed the display and cache range are released. A value less than 0 evaluates to the default value.
393
394**Atomic service API**: This API can be used in atomic services since API version 12.
395
396**System capability**: SystemCapability.ArkUI.ArkUI.Full
397
398**Parameters**
399
400| Name| Type  | Mandatory| Description                                    |
401| ------ | ------ | ---- | ---------------------------------------- |
402| value  | number | Yes  | Number of water flow items to be preloaded (cached).<br> Default value: **1**|
403
404## Events
405
406In addition to [universal events](ts-universal-events-click.md) and [scrollable component common events](ts-container-scrollable-common.md#events), the following events are also supported.
407
408### onReachStart
409
410onReachStart(event: () => void)
411
412Triggered when the component reaches the start.
413
414**Atomic service API**: This API can be used in atomic services since API version 11.
415
416**System capability**: SystemCapability.ArkUI.ArkUI.Full
417
418### onReachEnd
419
420onReachEnd(event: () => void)
421
422Triggered when the component reaches the end position.
423
424**Atomic service API**: This API can be used in atomic services since API version 11.
425
426**System capability**: SystemCapability.ArkUI.ArkUI.Full
427
428### onScrollFrameBegin<sup>10+</sup>
429
430onScrollFrameBegin(event: (offset: number, state: ScrollState) => { offsetRemain: number; })
431
432Triggered when the component starts to scroll. The input parameters indicate the amount by which the component will scroll. The event handler then works out the amount by which the component needs to scroll based on the real-world situation and returns the result.
433
434This event is triggered when the user starts dragging the component or the component starts inertial scrolling. It is not triggered when the component rebounds, the scrolling controller is used, or the scrollbar is dragged.
435
436**Atomic service API**: This API can be used in atomic services since API version 11.
437
438**System capability**: SystemCapability.ArkUI.ArkUI.Full
439
440**Parameters**
441
442| Name| Type                                                   | Mandatory| Description                      |
443| ------ | ------------------------------------------------------- | ---- | -------------------------- |
444| offset | number                                                  | Yes  | Amount to scroll by, in vp.|
445| state  | [ScrollState](ts-container-list.md#scrollstate) | Yes  | Current scroll state.            |
446
447**Return value**
448
449| Type                    | Description                |
450| ------------------------ | -------------------- |
451| { offsetRemain: number } | Actual amount by which the component scrolls, in vp.|
452
453### onScrollIndex<sup>11+</sup>
454
455onScrollIndex(event: (first: number, last: number) => void)
456
457Triggered when the first or last item displayed in the component changes. It is triggered once when the component is initialized.
458
459This event is triggered when either of the preceding indexes changes.
460
461**Atomic service API**: This API can be used in atomic services since API version 11.
462
463**System capability**: SystemCapability.ArkUI.ArkUI.Full
464
465**Parameters**
466
467| Name| Type  | Mandatory| Description                                 |
468| ------ | ------ | ---- | ------------------------------------- |
469| first  | number | Yes  | Index of the first item of the component.|
470| last   | number | Yes  | Index of the last item of the component.   |
471
472## Example
473
474### Example 1
475This example shows the basic usage of **WaterFlow**:
476```ts
477// WaterFlowDataSource.ets
478
479// Object that implements the IDataSource API, which is used by the WaterFlow component to load data.
480export class WaterFlowDataSource implements IDataSource {
481  private dataArray: number[] = []
482  private listeners: DataChangeListener[] = []
483
484  constructor() {
485    for (let i = 0; i < 100; i++) {
486      this.dataArray.push(i)
487    }
488  }
489
490  // Obtain the data corresponding to the specified index.
491  public getData(index: number): number {
492    return this.dataArray[index]
493  }
494
495  // Notify the controller of data reloading.
496  notifyDataReload(): void {
497    this.listeners.forEach(listener => {
498      listener.onDataReloaded()
499    })
500  }
501
502  // Notify the controller of data addition.
503  notifyDataAdd(index: number): void {
504    this.listeners.forEach(listener => {
505      listener.onDataAdd(index)
506    })
507  }
508
509  // Notify the controller of data changes.
510  notifyDataChange(index: number): void {
511    this.listeners.forEach(listener => {
512      listener.onDataChange(index)
513    })
514  }
515
516  // Notify the controller of data deletion.
517  notifyDataDelete(index: number): void {
518    this.listeners.forEach(listener => {
519      listener.onDataDelete(index)
520    })
521  }
522
523  // Notify the controller of the data location change.
524  notifyDataMove(from: number, to: number): void {
525    this.listeners.forEach(listener => {
526      listener.onDataMove(from, to)
527    })
528  }
529
530  // Notify the controller of batch data modification.
531  notifyDatasetChange(operations: DataOperation[]): void {
532    this.listeners.forEach(listener => {
533      listener.onDatasetChange(operations);
534    })
535  }
536
537  // Obtain the total number of data records.
538  public totalCount(): number {
539    return this.dataArray.length
540  }
541
542  // Register the data change listener.
543  registerDataChangeListener(listener: DataChangeListener): void {
544    if (this.listeners.indexOf(listener) < 0) {
545      this.listeners.push(listener)
546    }
547  }
548
549  // Unregister the data change listener.
550  unregisterDataChangeListener(listener: DataChangeListener): void {
551    const pos = this.listeners.indexOf(listener)
552    if (pos >= 0) {
553      this.listeners.splice(pos, 1)
554    }
555  }
556
557  // Add data.
558  public add1stItem(): void {
559    this.dataArray.splice(0, 0, this.dataArray.length)
560    this.notifyDataAdd(0)
561  }
562
563  // Add an item to the end of the data.
564  public addLastItem(): void {
565    this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length)
566    this.notifyDataAdd(this.dataArray.length - 1)
567  }
568
569  // Add an item to the position corresponding to the specified index.
570  public addItem(index: number): void {
571    this.dataArray.splice(index, 0, this.dataArray.length)
572    this.notifyDataAdd(index)
573  }
574
575  // Delete the first item.
576  public delete1stItem(): void {
577    this.dataArray.splice(0, 1)
578    this.notifyDataDelete(0)
579  }
580
581  // Delete the second item.
582  public delete2ndItem(): void {
583    this.dataArray.splice(1, 1)
584    this.notifyDataDelete(1)
585  }
586
587  // Delete the last item.
588  public deleteLastItem(): void {
589    this.dataArray.splice(-1, 1)
590    this.notifyDataDelete(this.dataArray.length)
591  }
592
593  // Delete an item at the specified index position.
594  public deleteItem(index: number): void {
595    this.dataArray.splice(index, 1)
596    this.notifyDataDelete(index)
597  }
598
599  // Reload data.
600  public reload(): void {
601    this.dataArray.splice(1, 1)
602    this.dataArray.splice(3, 2)
603    this.notifyDataReload()
604  }
605}
606```
607
608```ts
609// Index.ets
610import { WaterFlowDataSource } from './WaterFlowDataSource'
611
612@Entry
613@Component
614struct WaterFlowDemo {
615  @State minSize: number = 80
616  @State maxSize: number = 180
617  @State fontSize: number = 24
618  @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]
619  scroller: Scroller = new Scroller()
620  dataSource: WaterFlowDataSource = new WaterFlowDataSource()
621  private itemWidthArray: number[] = []
622  private itemHeightArray: number[] = []
623
624  // Calculate the width and height of a water flow item.
625  getSize() {
626    let ret = Math.floor(Math.random() * this.maxSize)
627    return (ret > this.minSize ? ret : this.minSize)
628  }
629
630  // Set the width and height array of the water flow item.
631  setItemSizeArray() {
632    for (let i = 0; i < 100; i++) {
633      this.itemWidthArray.push(this.getSize())
634      this.itemHeightArray.push(this.getSize())
635    }
636  }
637
638  aboutToAppear() {
639    this.setItemSizeArray()
640  }
641
642  @Builder
643  itemFoot() {
644    Column() {
645      Text(`Footer`)
646        .fontSize(10)
647        .backgroundColor(Color.Red)
648        .width(50)
649        .height(50)
650        .align(Alignment.Center)
651        .margin({ top: 2 })
652    }
653  }
654
655  build() {
656    Column({ space: 2 }) {
657      WaterFlow() {
658        LazyForEach(this.dataSource, (item: number) => {
659          FlowItem() {
660            Column() {
661              Text("N" + item).fontSize(12).height('16')
662              // The image is displayed only when there is a corresponding JPG file.
663              Image('res/waterFlowTest(' + item % 5 + ').jpg')
664                .objectFit(ImageFit.Fill)
665                .width('100%')
666                .layoutWeight(1)
667            }
668          }
669          .onAppear(() => {
670            // Add data in advance when scrolling is about to end.
671            if (item + 20 == this.dataSource.totalCount()) {
672              for (let i = 0; i < 100; i++) {
673                this.dataSource.addLastItem()
674              }
675            }
676          })
677          .width('100%')
678          .height(this.itemHeightArray[item % 100])
679          .backgroundColor(this.colors[item % 5])
680        }, (item: string) => item)
681      }
682      .columnsTemplate("1fr 1fr")
683      .columnsGap(10)
684      .rowsGap(5)
685      .backgroundColor(0xFAEEE0)
686      .width('100%')
687      .height('100%')
688      .onReachStart(() => {
689        console.info('waterFlow reach start')
690      })
691      .onScrollStart(() => {
692        console.info('waterFlow scroll start')
693      })
694      .onScrollStop(() => {
695        console.info('waterFlow scroll stop')
696      })
697      .onScrollFrameBegin((offset: number, state: ScrollState) => {
698        console.info('waterFlow scrollFrameBegin offset: ' + offset + ' state: ' + state.toString())
699        return { offsetRemain: offset }
700      })
701    }
702  }
703}
704```
705
706![zh-cn_image_WaterFlow.gif](figures/waterflow-perf-demo.gif)
707
708### Example 2
709This example shows the usage of **auto-fill**:
710```ts
711//index.ets
712import { WaterFlowDataSource } from './WaterFlowDataSource'
713
714@Entry
715@Component
716struct WaterFlowDemo {
717  @State minSize: number = 80
718  @State maxSize: number = 180
719  @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]
720  dataSource: WaterFlowDataSource = new WaterFlowDataSource()
721  private itemWidthArray: number[] = []
722  private itemHeightArray: number[] = []
723
724  // Calculate the width and height of a water flow item.
725  getSize() {
726    let ret = Math.floor(Math.random() * this.maxSize)
727    return (ret > this.minSize ? ret : this.minSize)
728  }
729
730  // Set the width and height array of the water flow item.
731  setItemSizeArray() {
732    for (let i = 0; i < 100; i++) {
733      this.itemWidthArray.push(this.getSize())
734      this.itemHeightArray.push(this.getSize())
735    }
736  }
737
738  aboutToAppear() {
739    this.setItemSizeArray()
740  }
741
742  build() {
743    Column({ space: 2 }) {
744      WaterFlow() {
745        LazyForEach(this.dataSource, (item: number) => {
746          FlowItem() {
747            Column() {
748              Text("N" + item).fontSize(12).height('16')
749              Image('res/waterFlowTest(' + item % 5 + ').jpg')
750            }
751          }
752          .width('100%')
753          .height(this.itemHeightArray[item % 100])
754          .backgroundColor(this.colors[item % 5])
755        }, (item: string) => item)
756      }
757      .columnsTemplate('repeat(auto-fill,80)')
758      .columnsGap(10)
759      .rowsGap(5)
760      .padding({left:5})
761      .backgroundColor(0xFAEEE0)
762      .width('100%')
763      .height('100%')
764    }
765  }
766}
767```
768
769![waterflow_auto-fill.png](figures/waterflow_auto-fill.png)
770
771
772### Example 3
773This example shows how to use **WaterFlowSections**.
774```ts
775// Index.ets
776import { WaterFlowDataSource } from './WaterFlowDataSource'
777
778@Reusable
779@Component
780struct ReusableFlowItem {
781  @State item: number = 0
782
783  // Invoked when a reusable custom component is re-added to the component tree from the reuse cache. The component state variable can be updated here to display the correct content.
784  aboutToReuse(params: Record<string, number>) {
785    this.item = params.item;
786    console.info('Reuse item:' + this.item)
787  }
788
789  aboutToAppear() {
790    console.info('new item:' + this.item)
791  }
792
793  build() {
794    Image('res/waterFlowTest(' + this.item % 5 + ').jpg')
795        .overlay('N' + this.item, { align: Alignment.Top })
796        .objectFit(ImageFit.Fill)
797        .width('100%')
798        .layoutWeight(1)
799  }
800}
801
802@Entry
803@Component
804struct WaterFlowDemo {
805  minSize: number = 80
806  maxSize: number = 180
807  fontSize: number = 24
808  colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]
809  scroller: Scroller = new Scroller()
810  dataSource: WaterFlowDataSource = new WaterFlowDataSource()
811  dataCount: number = this.dataSource.totalCount()
812  private itemHeightArray: number[] = []
813  @State sections: WaterFlowSections = new WaterFlowSections()
814  sectionMargin: Margin = { top: 10, left: 5, bottom: 10, right: 5 }
815  oneColumnSection: SectionOptions = {
816    itemsCount: 4,
817    crossCount: 1,
818    columnsGap: '5vp',
819    rowsGap: 10,
820    margin: this.sectionMargin,
821    onGetItemMainSizeByIndex: (index: number) => {
822      return this.itemHeightArray[index % 100]
823    }
824  }
825  twoColumnSection: SectionOptions = {
826    itemsCount: 2,
827    crossCount: 2,
828    onGetItemMainSizeByIndex: (index: number) => {
829      return 100
830    }
831  }
832  lastSection: SectionOptions = {
833    itemsCount: 20,
834    crossCount: 2,
835    onGetItemMainSizeByIndex: (index: number) => {
836      return this.itemHeightArray[index % 100]
837    }
838  }
839
840  // Calculate the FlowItem height.
841  getSize() {
842    let ret = Math.floor(Math.random() * this.maxSize)
843    return (ret > this.minSize ? ret : this.minSize)
844  }
845
846  // Set the height array for FlowItems.
847  setItemSizeArray() {
848    for (let i = 0; i < 100; i++) {
849      this.itemHeightArray.push(this.getSize())
850    }
851  }
852
853  aboutToAppear() {
854    this.setItemSizeArray()
855    // Initialize the water flow section information.
856    let sectionOptions: SectionOptions[] = []
857    let count = 0
858    let oneOrTwo = 0
859    while (count < this.dataCount) {
860      if (this.dataCount - count < 20) {
861        this.lastSection.itemsCount = this.dataCount - count
862        sectionOptions.push(this.lastSection)
863        break;
864      }
865      if (oneOrTwo++ % 2 == 0) {
866        sectionOptions.push(this.oneColumnSection)
867        count += this.oneColumnSection.itemsCount
868      } else {
869        sectionOptions.push(this.twoColumnSection)
870        count += this.twoColumnSection.itemsCount
871      }
872    }
873    this.sections.splice(0, 0, sectionOptions)
874  }
875
876  build() {
877    Column({ space: 2 }) {
878      Row() {
879        Button('splice')
880          .height('5%')
881          .onClick(() => {
882            // Replace all sections with a new section. Ensure that the number of data array items in LazyForEach is the same as the value of itemsCount of the new section.
883            let totalCount: number = this.dataSource.totalCount()
884            let newSection: SectionOptions = {
885              itemsCount: totalCount,
886              crossCount: 2,
887              onGetItemMainSizeByIndex: (index: number) => {
888                return this.itemHeightArray[index % 100]
889              }
890            }
891            let oldLength: number = this.sections.length()
892            this.sections.splice(0, oldLength, [newSection])
893          })
894          .margin({ top: 10, left: 20 })
895        Button('update')
896          .height('5%')
897          .onClick(() => {
898            // Add four FlowItems to the second section. Ensure that the number of data array items in LazyForEach is the same as the sum of itemsCount values of all sections.
899            let newSection: SectionOptions = {
900              itemsCount: 6,
901              crossCount: 3,
902              columnsGap: 5,
903              rowsGap: 10,
904              margin: this.sectionMargin,
905              onGetItemMainSizeByIndex: (index: number) => {
906                return this.itemHeightArray[index % 100]
907              }
908            }
909            this.dataSource.addItem(this.oneColumnSection.itemsCount)
910            this.dataSource.addItem(this.oneColumnSection.itemsCount + 1)
911            this.dataSource.addItem(this.oneColumnSection.itemsCount + 2)
912            this.dataSource.addItem(this.oneColumnSection.itemsCount + 3)
913            const result: boolean = this.sections.update(1, newSection)
914            console.info('update:' + result)
915          })
916          .margin({ top: 10, left: 20 })
917        Button('delete')
918          .height('5%')
919          .onClick(() => {
920            // Click Update and then Delete.
921            let newSection: SectionOptions = {
922              itemsCount: 2,
923              crossCount: 2,
924              columnsGap: 5,
925              rowsGap: 10,
926              margin: this.sectionMargin,
927              onGetItemMainSizeByIndex: (index: number) => {
928                return this.itemHeightArray[index % 100]
929              }
930            }
931            this.dataSource.deleteItem(this.oneColumnSection.itemsCount)
932            this.dataSource.deleteItem(this.oneColumnSection.itemsCount)
933            this.dataSource.deleteItem(this.oneColumnSection.itemsCount)
934            this.dataSource.deleteItem(this.oneColumnSection.itemsCount)
935            this.sections.update(1, newSection)
936          })
937          .margin({ top: 10, left: 20 })
938        Button('values')
939          .height('5%')
940          .onClick(() => {
941            const sections: Array<SectionOptions> = this.sections.values();
942            for (const value of sections) {
943              console.log(JSON.stringify(value));
944            }
945            console.info('count:' + this.sections.length())
946          })
947          .margin({ top: 10, left: 20 })
948      }.margin({ bottom: 20 })
949
950      WaterFlow({ scroller: this.scroller, sections: this.sections }) {
951        LazyForEach(this.dataSource, (item: number) => {
952          FlowItem() {
953            ReusableFlowItem({ item: item })
954          }
955          .width('100%')
956          // The value of onGetItemMainSizeByIndex is used.
957          // .height(this.itemHeightArray[item % 100])
958          .backgroundColor(this.colors[item % 5])
959        }, (item: string) => item)
960      }
961      .columnsTemplate('1fr 1fr') // This attribute is ineffective when the sections parameter is used.
962      .columnsGap(10)
963      .rowsGap(5)
964      .backgroundColor(0xFAEEE0)
965      .width('100%')
966      .height('100%')
967      .layoutWeight(1)
968      .onScrollIndex((first: number, last: number) => {
969        // Add data in advance when scrolling is about to end.
970        if (last + 20 >= this.dataSource.totalCount()) {
971          for (let i = 0; i < 100; i++) {
972            this.dataSource.addLastItem()
973          }
974          // After the data source is updated, update sections synchronously and change the number of FlowItems in the last section.
975          const sections: Array<SectionOptions> = this.sections.values();
976          let newSection: SectionOptions = sections[this.sections.length() - 1];
977          newSection.itemsCount += 100;
978          this.sections.update(-1, newSection);
979        }
980      })
981    }
982  }
983}
984```
985
986![waterflowSections.png](figures/waterflowSections.png)
987
988### Example 4
989This example implements the feature of using the pinch gesture to change the number of columns.
990
991```ts
992// Index.ets
993import { WaterFlowDataSource } from './WaterFlowDataSource'
994
995@Reusable
996@Component
997struct ReusableFlowItem {
998  @State item: number = 0
999
1000  // Invoked when a reusable custom component is re-added to the component tree from the reuse cache. The component state variable can be updated here to display the correct content.
1001  aboutToReuse(params: Record<string, number>) {
1002    this.item = params.item;
1003    console.info('Reuse item:' + this.item)
1004  }
1005
1006  aboutToAppear() {
1007    console.info('item:' + this.item)
1008  }
1009
1010  build() {
1011    Column() {
1012      Text("N" + this.item).fontSize(12).height('16')
1013      Image('res/waterFlow (' + this.item % 5 + ').JPG')
1014        .objectFit(ImageFit.Fill)
1015        .width('100%')
1016        .layoutWeight(1)
1017    }
1018  }
1019}
1020
1021@Entry
1022@Component
1023struct WaterFlowDemo {
1024  minSize: number = 80
1025  maxSize: number = 180
1026  colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]
1027  @State columns: number = 2
1028  dataSource: WaterFlowDataSource = new WaterFlowDataSource()
1029  private itemWidthArray: number[] = []
1030  private itemHeightArray: number[] = []
1031
1032  // Calculate the width and height of a water flow item.
1033  getSize() {
1034    let ret = Math.floor(Math.random() * this.maxSize)
1035    return (ret > this.minSize ? ret : this.minSize)
1036  }
1037
1038  // Set the width and height array of the water flow item.
1039  setItemSizeArray() {
1040    for (let i = 0; i < 100; i++) {
1041      this.itemWidthArray.push(this.getSize())
1042      this.itemHeightArray.push(this.getSize())
1043    }
1044  }
1045
1046  aboutToAppear() {
1047    let lastCount = AppStorage.get<number>('columnsCount')
1048    if (typeof lastCount != 'undefined') {
1049      this.columns = lastCount
1050    }
1051    this.setItemSizeArray()
1052  }
1053
1054  build() {
1055    Column({ space: 2 }) {
1056      Row() {
1057        Text('Pinch to change the number of columns')
1058          .height('5%')
1059          .margin({ top: 10, left: 20 })
1060      }
1061
1062      WaterFlow() {
1063        LazyForEach(this.dataSource, (item: number) => {
1064          FlowItem() {
1065            ReusableFlowItem({ item: item })
1066          }
1067          .width('100%')
1068          .height(this.itemHeightArray[item % 100])
1069          .backgroundColor(this.colors[item % 5])
1070        }, (item: string) => item)
1071      }
1072      .columnsTemplate('1fr '.repeat(this.columns))
1073      .columnsGap(10)
1074      .rowsGap(5)
1075      .backgroundColor(0xFAEEE0)
1076      .width('100%')
1077      .height('100%')
1078      .layoutWeight(1)
1079      // Switching the number of columns triggers a reordering animation for the item positions.
1080      .animation({
1081        duration: 300,
1082        curve: Curve.Smooth
1083      })
1084      .priorityGesture(
1085        PinchGesture()
1086          .onActionEnd((event: GestureEvent) => {
1087            console.info('end scale:' + event.scale)
1088            // When a user performs a pinch-to-zoom gesture by moving their fingers apart, and the number of columns decreases to a certain threshold (in this case, 2), it will cause the items to enlarge.
1089            if (event.scale > 2) {
1090              this.columns--
1091            } else if (event.scale < 0.6) {
1092              this.columns++
1093            }
1094            // You can set the maximum and minimum number of columns based on the device screen width. Here, the minimum number of columns is 1, and the maximum number of columns is 4.
1095            this.columns = Math.min(4, Math.max(1, this.columns));
1096            AppStorage.setOrCreate<number>('columnsCount', this.columns)
1097          })
1098      )
1099    }
1100  }
1101}
1102```
1103
1104![pinch](figures/waterflow-pinch.gif)
1105