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 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 356