1# High-Performance WaterFlow Development 2 3## Background 4 5The waterfall layout is a popular layout for presenting images and frequently seen in shopping and information applications. It is implemented using the [WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md) component in ArkUI. This document discusses how to improve the **WaterFlow** performance, with practical examples. 6 7## Using Lazy Loading 8 9Below shows the basic usage of the **WaterFlow** component. 10 11```ts 12 build() { 13 Column({ space: 2 }) { 14 WaterFlow() { 15 LazyForEach(this.datasource, (item: number) => { 16 FlowItem() { 17 Column() { 18 Text("N" + item).fontSize(12).height('16') 19 Image('res/waterFlowTest (' + item % 5 + ').jpg') 20 .objectFit(ImageFit.Fill) 21 .width('100%') 22 .layoutWeight(1) 23 } 24 } 25 .width('100%') 26 // Set the <FlowItem> height to avoid the need to adapt to image heights. 27 .height(this.itemHeightArray[item]) 28 .backgroundColor(this.colors[item % 5]) 29 }, (item: string) => item) 30 } 31 .columnsTemplate("1fr 1fr") 32 .columnsGap(10) 33 .rowsGap(5) 34 .backgroundColor(0xFAEEE0) 35 .width('100%') 36 .height('80%') 37 } 38 } 39``` 40 41In the sample code, [LazyForEach](../quick-start/arkts-rendering-control-lazyforeach.md) is used for lazy loading. During the layout of the **WaterFlow** component, the **FlowItem** components are created as needed based on the visible area; those that extend beyond the visible area are destroyed to reduce memory usage. 42 43Considering that **Image** components are loaded asynchronously by default, you are advised to set the height for **FlowItem** components based on the image size, to avoid layout re-render caused by **FlowItem** components' changing heights to accommodate images. 44 45## Implementing Infinite Scrolling 46 47In the example, the fixed number of **FlowItem** components results in failure to achieve infinite scrolling. 48 49To implement infinite scrolling with the capabilities provided by the **WaterFlow** component, you can new data to the **LazyForEach** data source during **onReachEnd**, and set the footer to the loading-new-data style (by using the [\<LoadingProgress>](../reference/apis-arkui/arkui-ts/ts-basic-components-loadingprogress.md) component). 50 51```ts 52 build() { 53 Column({ space: 2 }) { 54 WaterFlow({ footer: this.itemFoot.bind(this) }) { 55 LazyForEach(this.datasource, (item: number) => { 56 FlowItem() { 57 Column() { 58 Text("N" + item).fontSize(12).height('16') 59 Image('res/waterFlowTest (' + item % 5 + ').jpg') 60 .objectFit(ImageFit.Fill) 61 .width('100%') 62 .layoutWeight(1) 63 } 64 } 65 .width('100%') 66 .height(this.itemHeightArray[item % 100]) 67 .backgroundColor(this.colors[item % 5]) 68 }, (item: string) => item) 69 } 70 // Load data once the scrolling reaches the end of the page. 71 .onReachEnd(() => { 72 console.info("onReachEnd") 73 setTimeout(() => { 74 for (let i = 0; i < 100; i++) { 75 this.datasource.AddLastItem() 76 } 77 }, 1000) 78 }) 79 .columnsTemplate("1fr 1fr") 80 .columnsGap(10) 81 .rowsGap(5) 82 .backgroundColor(0xFAEEE0) 83 .width('100%') 84 .height('80%') 85 } 86 } 87 88 // Add an item to the end of the data. 89 public AddLastItem(): void { 90 this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length) 91 this.notifyDataAdd(this.dataArray.length - 1) 92 } 93``` 94 95To add new data, you must add an item to the end of the data. Do not directly modify the data array and use **onDataReloaded()** of **LazyForEach** to instruct the **WaterFlow** component to reload data. 96 97Because the heights of the child components in **WaterFlow** vary, and the position of a lower child component depends on its upper one, reloading all data in **WaterFlow** will trigger waterfall layout recalculation, causing frame freezing. In comparison, if you add new data by adding an item to the end of the data and then call **notifyDataAdd(this.dataArray.length - 1)**, the **WaterFlow** component loads new data, without processing existing data repeatedly. 98 99 100 101## Adding Data in Advance 102 103Although infinite scrolling can be achieved through triggering of **onReachEnd()** upon new data, there may be an obvious pause in the process of loading new data when the user scrolls to the bottom. 104 105To create a smooth scrolling experience, you need to adjust the time for adding new data. For example, you can add some new data in advance when the **LazyForEach** data source still has several pieces of data left before iteration ends. 106 107```ts 108 build() { 109 Column({ space: 2 }) { 110 WaterFlow() { 111 LazyForEach(this.datasource, (item: number) => { 112 FlowItem() { 113 Column() { 114 Text("N" + item).fontSize(12).height('16') 115 Image('res/waterFlowTest (' + item % 5 + ').jpg') 116 .objectFit(ImageFit.Fill) 117 .width('100%') 118 .layoutWeight(1) 119 } 120 } 121 .onAppear(() => { 122 // Add data in advance when scrolling is about to end. 123 if (item + 20 == this.datasource.totalCount()) { 124 for (let i = 0; i < 100; i++) { 125 this.datasource.AddLastItem() 126 } 127 } 128 }) 129 .width('100%') 130 .height(this.itemHeightArray[item % 100]) 131 .backgroundColor(this.colors[item % 5]) 132 }, (item: string) => item) 133 } 134 .columnsTemplate("1fr 1fr") 135 .columnsGap(10) 136 .rowsGap(5) 137 .backgroundColor(0xFAEEE0) 138 .width('100%') 139 .height('80%') 140 } 141 } 142``` 143 144In this example, the quantity of data items left till the end is determined in **onAppear** of **FlowItem**, and new data is added in advance to implement stutter-free infinite scrolling. 145 146 147 148## Reusing Components 149 150Now that we have a waterfall that scrolls infinitely without explicitly waiting for more data, we can further optimize its performance by making the components reusable. 151 152During scrolling, **FlowItem** and its child component are frequently created and destroyed. By encapsulating the component in **FlowItem** into a custom component and decorating it with the **@Reusable** decorator, you make the component reusable, reducing the overhead of repeatedly creating and destroying nodes in the ArkUI framework. For details about component reuse, see [Best Practices for Component Reuse](component-recycle.md). 153 154```ts 155 build() { 156 Column({ space: 2 }) { 157 WaterFlow() { 158 LazyForEach(this.datasource, (item: number) => { 159 FlowItem() { 160 // Use reusable custom components. 161 ResuableFlowItem({ item: item }) 162 } 163 .onAppear(() => { 164 // Add data in advance when scrolling is about to end. 165 if (item + 20 == this.datasource.totalCount()) { 166 for (let i = 0; i < 100; i++) { 167 this.datasource.AddLastItem() 168 } 169 } 170 }) 171 .width('100%') 172 .height(this.itemHeightArray[item % 100]) 173 .backgroundColor(this.colors[item % 5]) 174 }, (item: string) => item) 175 } 176 .columnsTemplate("1fr 1fr") 177 .columnsGap(10) 178 .rowsGap(5) 179 .backgroundColor(0xFAEEE0) 180 .width('100%') 181 .height('80%') 182 } 183 } 184@Reusable 185@Component 186struct ResuableFlowItem { 187 @State item: number = 0 188 189 // Invoked when a reusable custom component is re-added to the component tree from the reuse cache. The component state variable can be updated here to display the correct content. 190 aboutToReuse(params) { 191 this.item = params.item; 192 } 193 194 build() { 195 Column() { 196 Text("N" + this.item).fontSize(12).height('16') 197 Image('res/waterFlowTest (' + this.item % 5 + ').jpg') 198 .objectFit(ImageFit.Fill) 199 .width('100%') 200 .layoutWeight(1) 201 } 202 } 203} 204 205``` 206 207## Takeaway 208 209To achieve the optimal performance in infinite scrolling, you can use **WaterFlow** with the **LazyForEach** rendering control syntax, ahead-of-time data addition, and component reuse. 210