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 301