• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# WaterFlow
2
3
4The **\<WaterFlow>** component is a 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
15This component can contain the [\<FlowItem>](ts-container-flowitem.md) child component.
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
23
24WaterFlow(options?:  WaterFlowOptions)
25
26**Parameters**
27
28| Name| Type| Mandatory| Description|
29| -------- | -------- | -------- | -------- |
30| options |  [WaterFlowOptions](#waterflowoptions)| Yes| Parameters of the **\<WaterFlow>** component.|
31
32
33## WaterFlowOptions
34
35
36| Name    | Type                                       | Mandatory| Description                                    |
37| ---------- | ----------------------------------------------- | ------ | -------------------------------------------- |
38| footer |  [CustomBuilder](ts-types.md#custombuilder8) | No  | Footer of the **\<WaterFlow>** component. |
39| scroller | [Scroller](ts-container-scroll.md#scroller) | No  | Controller, which can be bound to scrollable components.<br>**NOTE**<br>The 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).|
40
41
42## Attributes
43
44
45In addition to the [universal attributes](ts-universal-attributes-size.md), the following attributes are supported.
46
47| Name| Type| Description|
48| -------- | -------- | -------- |
49| columnsTemplate | string | Number of columns in the layout. If this attribute is not set, one column is used by default.<br>For 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.<br>You 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.<br>Default value: **'1fr'**|
50| rowsTemplate | string | Number of rows in the layout. If this attribute is not set, one row is used by default.<br>For 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.<br>You 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.<br>Default value: **'1fr'**|
51| itemConstraintSize | [ConstraintSizeOptions](ts-types.md#constraintsizeoptions) | Size constraints of the child components during layout.              |
52| columnsGap | [Length](ts-types.md#length) |Gap between columns.<br>Default value: **0**|
53| rowsGap | [Length](ts-types.md#length) |Gap between rows.<br> Default value: **0**|
54| layoutDirection | [FlexDirection](ts-appendix-enums.md#flexdirection) |Main axis direction of the layout.<br>Default value: **FlexDirection.Column**|
55| enableScrollInteraction<sup>10+</sup>  |  boolean  |   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.<br>Default value: **true**     |
56| nestedScroll<sup>10+</sup>                 | [NestedScrollOptions](ts-container-scroll.md#nestedscrolloptions10)         | Nested scrolling options. You can set the nested scrolling mode in the forward and backward directions to implement scrolling linkage with the parent component.|
57| friction<sup>10+</sup> | number \| [Resource](ts-types.md#resource)    | Friction coefficient. It applies only to gestures in the scrolling area, and it affects only indirectly the scroll chaining during the inertial scrolling process.<br>Default value: **0.9** for wearable devices and **0.6** for non-wearable devices<br>**NOTE**<br>A value less than or equal to 0 evaluates to the default value.|
58
59The priority of **layoutDirection** is higher than that of **rowsTemplate** and **columnsTemplate**. Depending on the **layoutDirection** settings, there are three layout modes:
60
61- **layoutDirection** is set to **FlexDirection.Column** or **FlexDirection.ColumnReverse**
62
63	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.
64
65- **layoutDirection** set to **FlexDirection.Row** or **FlexDirection.RowReverse**
66
67	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.
68
69- **layoutDirection** is not set
70
71	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.
72
73## Events
74
75
76In addition to the [universal events](ts-universal-events-click.md), the following events are supported.
77
78
79| Name| Description|
80| -------- | -------- |
81| onReachStart(event: () => void) | Triggered when the component reaches the start.|
82| onReachEnd(event: () => void)   | Triggered when the component reaches the end position.|
83| onScrollFrameBegin<sup>10+</sup>(event: (offset: number, state:  [ScrollState](ts-container-list.md#scrollstate) => { offsetRemain: number }) | Triggered 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.<br>\- **offset**: amount to scroll by, in vp.<br>\- **state**: current scrolling state.<br>- **offsetRemain**: actual amount by which the component scrolls, in vp.<br>This 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.|
84
85
86## Example
87
88### Example 1
89Basic usage of **\<WaterFlow>**:
90```ts
91// WaterFlowDataSource.ets
92
93// Object that implements the IDataSource API, which is used by the <WaterFlow> component to load data.
94export class WaterFlowDataSource implements IDataSource {
95  private dataArray: number[] = []
96  private listeners: DataChangeListener[] = []
97
98  constructor() {
99    for (let i = 0; i < 100; i++) {
100      this.dataArray.push(i)
101    }
102  }
103
104  // Obtain the data corresponding to the specified index.
105  public getData(index: number): number {
106    return this.dataArray[index]
107  }
108
109  // Notify the controller of data reloading.
110  notifyDataReload(): void {
111    this.listeners.forEach(listener => {
112      listener.onDataReloaded()
113    })
114  }
115
116  // Notify the controller of data addition.
117  notifyDataAdd(index: number): void {
118    this.listeners.forEach(listener => {
119      listener.onDataAdd(index)
120    })
121  }
122
123  // Notify the controller of data changes.
124  notifyDataChange(index: number): void {
125    this.listeners.forEach(listener => {
126      listener.onDataChange(index)
127    })
128  }
129
130  // Notify the controller of data deletion.
131  notifyDataDelete(index: number): void {
132    this.listeners.forEach(listener => {
133      listener.onDataDelete(index)
134    })
135  }
136
137  // Notify the controller of the data location change.
138  notifyDataMove(from: number, to: number): void {
139    this.listeners.forEach(listener => {
140      listener.onDataMove(from, to)
141    })
142  }
143
144  // Obtain the total number of data records.
145  public totalCount(): number {
146    return this.dataArray.length
147  }
148
149  // Register the data change listener.
150  registerDataChangeListener(listener: DataChangeListener): void {
151    if (this.listeners.indexOf(listener) < 0) {
152      this.listeners.push(listener)
153    }
154  }
155
156  // Unregister the data change listener.
157  unregisterDataChangeListener(listener: DataChangeListener): void {
158    const pos = this.listeners.indexOf(listener)
159    if (pos >= 0) {
160      this.listeners.splice(pos, 1)
161    }
162  }
163
164  // Add data.
165  public add1stItem(): void {
166    this.dataArray.splice(0, 0, this.dataArray.length)
167    this.notifyDataAdd(0)
168  }
169
170  // Add an item to the end of the data.
171  public addLastItem(): void {
172    this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length)
173    this.notifyDataAdd(this.dataArray.length - 1)
174  }
175
176  // Add an item to the position corresponding to the specified index.
177  public addItem(index: number): void {
178    this.dataArray.splice(index, 0, this.dataArray.length)
179    this.notifyDataAdd(index)
180  }
181
182  // Delete the first item.
183  public delete1stItem(): void {
184    this.dataArray.splice(0, 1)
185    this.notifyDataDelete(0)
186  }
187
188  // Delete the second item.
189  public delete2ndItem(): void {
190    this.dataArray.splice(1, 1)
191    this.notifyDataDelete(1)
192  }
193
194  // Delete the last item.
195  public deleteLastItem(): void {
196    this.dataArray.splice(-1, 1)
197    this.notifyDataDelete(this.dataArray.length)
198  }
199
200  // Reload data.
201  public reload(): void {
202    this.dataArray.splice(1, 1)
203    this.dataArray.splice(3, 2)
204    this.notifyDataReload()
205  }
206}
207```
208
209```ts
210// Index.ets
211import { WaterFlowDataSource } from './WaterFlowDataSource'
212
213@Entry
214@Component
215struct WaterFlowDemo {
216  @State minSize: number = 80
217  @State maxSize: number = 180
218  @State fontSize: number = 24
219  @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]
220  scroller: Scroller = new Scroller()
221  dataSource: WaterFlowDataSource = new WaterFlowDataSource()
222  private itemWidthArray: number[] = []
223  private itemHeightArray: number[] = []
224
225  // Calculate the width and height of a <FlowItem>.
226  getSize() {
227    let ret = Math.floor(Math.random() * this.maxSize)
228    return (ret > this.minSize ? ret : this.minSize)
229  }
230
231  // Set the width and height array of the <FlowItem>.
232  setItemSizeArray() {
233    for (let i = 0; i < 100; i++) {
234      this.itemWidthArray.push(this.getSize())
235      this.itemHeightArray.push(this.getSize())
236    }
237  }
238
239  aboutToAppear() {
240    this.setItemSizeArray()
241  }
242
243  @Builder
244  itemFoot() {
245    Column() {
246      Text(`Footer`)
247        .fontSize(10)
248        .backgroundColor(Color.Red)
249        .width(50)
250        .height(50)
251        .align(Alignment.Center)
252        .margin({ top: 2 })
253    }
254  }
255
256  build() {
257    Column({ space: 2 }) {
258      WaterFlow() {
259        LazyForEach(this.dataSource, (item: number) => {
260          FlowItem() {
261            Column() {
262              Text("N" + item).fontSize(12).height('16')
263              Image('res/waterFlowTest(' + item % 5 + ').jpg')
264                .objectFit(ImageFit.Fill)
265                .width('100%')
266                .layoutWeight(1)
267            }
268          }
269          .onAppear(() => {
270            // Add data in advance when scrolling is about to end.
271            if (item + 20 == this.dataSource.totalCount()) {
272              for (let i = 0; i < 100; i++) {
273                this.dataSource.addLastItem()
274              }
275            }
276          })
277          .width('100%')
278          .height(this.itemHeightArray[item % 100])
279          .backgroundColor(this.colors[item % 5])
280        }, (item: string) => item)
281      }
282      .columnsTemplate("1fr 1fr")
283      .columnsGap(10)
284      .rowsGap(5)
285      .backgroundColor(0xFAEEE0)
286      .width('100%')
287      .height('100%')
288    }
289  }
290}
291```
292
293![zh-cn_image_WaterFlow.gif](figures/waterflow-perf-demo.gif)
294
295### Example 2
296Example of using **auto-fill**:
297```ts
298//index.ets
299import { WaterFlowDataSource } from './WaterFlowDataSource'
300
301@Entry
302@Component
303struct WaterFlowDemo {
304  @State minSize: number = 80
305  @State maxSize: number = 180
306  @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]
307  dataSource: WaterFlowDataSource = new WaterFlowDataSource()
308  private itemWidthArray: number[] = []
309  private itemHeightArray: number[] = []
310
311  // Calculate the width and height of a <FlowItem>.
312  getSize() {
313    let ret = Math.floor(Math.random() * this.maxSize)
314    return (ret > this.minSize ? ret : this.minSize)
315  }
316
317  // Set the width and height array of the <FlowItem>.
318  setItemSizeArray() {
319    for (let i = 0; i < 100; i++) {
320      this.itemWidthArray.push(this.getSize())
321      this.itemHeightArray.push(this.getSize())
322    }
323  }
324
325  aboutToAppear() {
326    this.setItemSizeArray()
327  }
328
329  build() {
330    Column({ space: 2 }) {
331      WaterFlow() {
332        LazyForEach(this.dataSource, (item: number) => {
333          FlowItem() {
334            Column() {
335              Text("N" + item).fontSize(12).height('16')
336            }
337          }
338          .width('100%')
339          .height(this.itemHeightArray[item % 100])
340          .backgroundColor(this.colors[item % 5])
341        }, (item: string) => item)
342      }
343      .columnsTemplate('repeat(auto-fill,80)')
344      .columnsGap(10)
345      .rowsGap(5)
346      .padding({left:5})
347      .backgroundColor(0xFAEEE0)
348      .width('100%')
349      .height('100%')
350    }
351  }
352}
353```
354
355![waterflow_auto-fill.png](figures/waterflow_auto-fill.png)
356