• 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
15The [\<FlowItem>](ts-container-flowitem.md) child component is supported.
16
17>  **NOTE**
18>
19>  When the **visibility** attribute of a child component in **\<WaterFlow >** is set to **None**, the child component is not displayed, but still takes up cells.
20
21## APIs
22
23
24WaterFlow(options?: {footer?: CustomBuilder, scroller?: Scroller})
25
26**Parameters**
27
28| Name    | Type                                       | Mandatory| Description                                    |
29| ---------- | ----------------------------------------------- | ------ | -------------------------------------------- |
30| footer |  [CustomBuilder](ts-types.md#custombuilder8) | No  | Footer of the **\<WaterFlow>** component. |
31| scroller | [Scroller](ts-container-scroll.md#scroller) | No  | Controller, which can be bound to scrollable components.<br>The **\<WaterFlow>** component supports only the **scrollToIndex** API of the **\<Scroller>** component.|
32
33
34## Attributes
35
36
37In addition to the [universal attributes](ts-universal-attributes-size.md), the following attributes are supported.
38
39| Name| Type| Description|
40| -------- | -------- | -------- |
41| 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. This attribute supports [auto-fill](#auto-fill).<br>Default value: **'1fr'**|
42| 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. This attribute supports [auto-fill](#auto-fill).<br>Default value: **'1fr'**|
43| itemConstraintSize | [ConstraintSizeOptions](ts-types.md#constraintsizeoptions) | Size constraints of the child components during layout.              |
44| columnsGap | Length |Gap between columns.<br>Default value: **0**|
45| rowsGap | Length |Gap between rows.<br> Default value: **0**|
46| layoutDirection | [FlexDirection](ts-appendix-enums.md#flexdirection) |Main axis direction of the layout.<br>Default value: **FlexDirection.Column**|
47| 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**     |
48| 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.|
49| 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.|
50
51The priority of **layoutDirection** is higher than that of **rowsTemplate** and **columnsTemplate**. Depending on the **layoutDirection** settings, there are three layout modes:
52
53- **layoutDirection** is set to **FlexDirection.Column** or **FlexDirection.ColumnReverse**
54
55	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.
56
57- **layoutDirection** set to **FlexDirection.Row** or **FlexDirection.RowReverse**
58
59	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.
60
61- **layoutDirection** is not set
62
63	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.
64
65## Events
66
67
68In addition to the [universal events](ts-universal-events-click.md), the following events are supported.
69
70
71| Name| Description|
72| -------- | -------- |
73| onReachStart(event: () => void) | Triggered when the component reaches the start.|
74| onReachEnd(event: () => void)   | Triggered when the component reaches the end position.|
75| onScrollFrameBegin<sup>10+</sup>(event: (offset: number, state: ScrollState) => { offsetRemain }) | 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. This event is not triggered when the component rebounds or the scrolling controller is used.|
76
77## auto-fill
78
79The **columnsTemplate** and **rowsTemplate** attributes supports **auto-fill** in the following format:
80
81```css
82repeat(auto-fill, track-size)
83```
84
85Where, **repeat** and **auto-fill** are keywords, and **track-size** indicates the row height or column width. The supported units include px, vp, %, and digits. The value of **track-size** must contain at least one valid row height or column width.
86
87
88## Example
89
90
91```ts
92// WaterFlowDataSource.ets
93
94// Object that implements the IDataSource API, which is used by the <WaterFlow> component to load data.
95export class WaterFlowDataSource implements IDataSource {
96  private dataArray: number[] = []
97  private listeners: DataChangeListener[] = []
98
99  constructor() {
100    for (let i = 0; i < 100; i++) {
101      this.dataArray.push(i)
102    }
103  }
104
105  // Obtain the data corresponding to the specified index.
106  public getData(index: number): number {
107    return this.dataArray[index]
108  }
109
110  // Notify the controller of data reloading.
111  notifyDataReload(): void {
112    this.listeners.forEach(listener => {
113      listener.onDataReloaded()
114    })
115  }
116
117  // Notify the controller of data addition.
118  notifyDataAdd(index: number): void {
119    this.listeners.forEach(listener => {
120      listener.onDataAdded(index)
121    })
122  }
123
124  // Notify the controller of data changes.
125  notifyDataChange(index: number): void {
126    this.listeners.forEach(listener => {
127      listener.onDataChanged(index)
128    })
129  }
130
131  // Notify the controller of data deletion.
132  notifyDataDelete(index: number): void {
133    this.listeners.forEach(listener => {
134      listener.onDataDeleted(index)
135    })
136  }
137
138  // Notify the controller of the data location change.
139  notifyDataMove(from: number, to: number): void {
140    this.listeners.forEach(listener => {
141      listener.onDataMoved(from, to)
142    })
143  }
144
145  // Obtain the total number of data records.
146  public totalCount(): number {
147    return this.dataArray.length
148  }
149
150  // Register the data change listener.
151  registerDataChangeListener(listener: DataChangeListener): void {
152    if (this.listeners.indexOf(listener) < 0) {
153      this.listeners.push(listener)
154    }
155  }
156
157  // Unregister the data change listener.
158  unregisterDataChangeListener(listener: DataChangeListener): void {
159    const pos = this.listeners.indexOf(listener)
160    if (pos >= 0) {
161      this.listeners.splice(pos, 1)
162    }
163  }
164
165  // Add data.
166  public Add1stItem(): void {
167    this.dataArray.splice(0, 0, this.dataArray.length)
168    this.notifyDataAdd(0)
169  }
170
171  // Add an item to the end of the data.
172  public AddLastItem(): void {
173    this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length)
174    this.notifyDataAdd(this.dataArray.length - 1)
175  }
176
177  // Add an item to the position corresponding to the specified index.
178  public AddItem(index: number): void {
179    this.dataArray.splice(index, 0, this.dataArray.length)
180    this.notifyDataAdd(index)
181  }
182
183  // Delete the first item.
184  public Delete1stItem(): void {
185    this.dataArray.splice(0, 1)
186    this.notifyDataDelete(0)
187  }
188
189  // Delete the second item.
190  public Delete2ndItem(): void {
191    this.dataArray.splice(1, 1)
192    this.notifyDataDelete(1)
193  }
194
195  // Delete the last item.
196  public DeleteLastItem(): void {
197    this.dataArray.splice(-1, 1)
198    this.notifyDataDelete(this.dataArray.length)
199  }
200
201  // Reload data.
202  public Reload(): void {
203    this.dataArray.splice(1, 1)
204    this.dataArray.splice(3, 2)
205    this.notifyDataReload()
206  }
207}
208```
209
210```ts
211// WaterflowDemo.ets
212import { WaterFlowDataSource } from './WaterFlowDataSource'
213
214@Entry
215@Component
216struct WaterflowDemo {
217  @State minSize: number = 50
218  @State maxSize: number = 100
219  @State fontSize: number = 24
220  @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]
221  scroller: Scroller = new Scroller()
222  datasource: WaterFlowDataSource = new WaterFlowDataSource()
223  private itemWidthArray: number[] = []
224  private itemHeightArray: number[] = []
225
226  // Calculate the width and height of a flow item.
227  getSize() {
228    let ret = Math.floor(Math.random() * this.maxSize)
229    return (ret > this.minSize ? ret : this.minSize)
230  }
231
232  // Save the width and height of the flow item.
233  getItemSizeArray() {
234    for (let i = 0; i < 100; i++) {
235      this.itemWidthArray.push(this.getSize())
236      this.itemHeightArray.push(this.getSize())
237    }
238  }
239
240  aboutToAppear() {
241    this.getItemSizeArray()
242  }
243
244  @Builder
245  itemFoot() {
246    Column() {
247      Text(`Footer`)
248        .fontSize(10)
249        .backgroundColor(Color.Red)
250        .width(50)
251        .height(50)
252        .align(Alignment.Center)
253        .margin({ top: 2 })
254    }
255  }
256
257  build() {
258    Column({ space: 2 }) {
259      WaterFlow({ footer: () => { this.itemFoot() }, scroller: this.scroller }) {
260        LazyForEach(this.datasource, (item: number) => {
261          FlowItem() {
262            Column() {
263              Text("N" + item).fontSize(12).height('16')
264              Image('res/waterFlowTest(' + item % 5 + ').jpg')
265                .objectFit(ImageFit.Fill)
266                .width('100%')
267                .layoutWeight(1)
268            }
269          }
270          .width('100%')
271          .height(this.itemHeightArray[item])
272          .backgroundColor(this.colors[item % 5])
273        }, (item: string) => item)
274      }
275      .columnsTemplate("1fr 1fr 1fr 1fr")
276      .itemConstraintSize({
277        minWidth: 0,
278        maxWidth: '100%',
279        minHeight: 0,
280        maxHeight: '100%'
281      })
282      .friction(0.6)
283      .columnsGap(10)
284      .rowsGap(5)
285      .onReachStart(() => {
286        console.info("onReachStart")
287      })
288      .onReachEnd(() => {
289        console.info("onReachEnd")
290      })
291      .backgroundColor(0xFAEEE0)
292      .width('100%')
293      .height('80%')
294      .layoutDirection(FlexDirection.Column)
295    }
296  }
297}
298```
299
300![en-us_image_WaterFlow.gif](figures/waterflow.gif)
301