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