1 2 3# High-Performance WaterFlow Development 4 5## Background 6 7The 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. 8 9## Using Lazy Loading 10 11Below shows the basic usage of the **\<WaterFlow>** component. 12 13```ts 14 build() { 15 Column({ space: 2 }) { 16 WaterFlow() { 17 LazyForEach(this.datasource, (item: number) => { 18 FlowItem() { 19 Column() { 20 Text("N" + item).fontSize(12).height('16') 21 Image('res/waterFlowTest (' + item % 5 + ').jpg') 22 .objectFit(ImageFit.Fill) 23 .width('100%') 24 .layoutWeight(1) 25 } 26 } 27 .width('100%') 28 // Set the <FlowItem> height to avoid the need to adapt to image heights. 29 .height(this.itemHeightArray[item]) 30 .backgroundColor(this.colors[item % 5]) 31 }, (item: string) => item) 32 } 33 .columnsTemplate("1fr 1fr") 34 .columnsGap(10) 35 .rowsGap(5) 36 .backgroundColor(0xFAEEE0) 37 .width('100%') 38 .height('80%') 39 } 40 } 41``` 42 43In 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. 44 45Considering 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. 46 47## Implementing Infinite Scrolling 48 49In the example, the fixed number of **\<FlowItem>** components results in failure to achieve infinite scrolling. 50 51To 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). 52 53```ts 54 build() { 55 Column({ space: 2 }) { 56 WaterFlow({ footer: this.itemFoot.bind(this) }) { 57 LazyForEach(this.datasource, (item: number) => { 58 FlowItem() { 59 Column() { 60 Text("N" + item).fontSize(12).height('16') 61 Image('res/waterFlowTest (' + item % 5 + ').jpg') 62 .objectFit(ImageFit.Fill) 63 .width('100%') 64 .layoutWeight(1) 65 } 66 } 67 .width('100%') 68 .height(this.itemHeightArray[item % 100]) 69 .backgroundColor(this.colors[item % 5]) 70 }, (item: string) => item) 71 } 72 // Load data once the scrolling reaches the end of the page. 73 .onReachEnd(() => { 74 console.info("onReachEnd") 75 setTimeout(() => { 76 for (let i = 0; i < 100; i++) { 77 this.datasource.AddLastItem() 78 } 79 }, 1000) 80 }) 81 .columnsTemplate("1fr 1fr") 82 .columnsGap(10) 83 .rowsGap(5) 84 .backgroundColor(0xFAEEE0) 85 .width('100%') 86 .height('80%') 87 } 88 } 89 90 // Add an item to the end of the data. 91 public AddLastItem(): void { 92 this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length) 93 this.notifyDataAdd(this.dataArray.length - 1) 94 } 95``` 96 97To 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. 98 99Because 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. 100 101 102 103## Adding Data in Advance 104 105Although 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. 106 107To 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. 108 109```ts 110 build() { 111 Column({ space: 2 }) { 112 WaterFlow() { 113 LazyForEach(this.datasource, (item: number) => { 114 FlowItem() { 115 Column() { 116 Text("N" + item).fontSize(12).height('16') 117 Image('res/waterFlowTest (' + item % 5 + ').jpg') 118 .objectFit(ImageFit.Fill) 119 .width('100%') 120 .layoutWeight(1) 121 } 122 } 123 .onAppear(() => { 124 // Add data in advance when scrolling is about to end. 125 if (item + 20 == this.datasource.totalCount()) { 126 for (let i = 0; i < 100; i++) { 127 this.datasource.AddLastItem() 128 } 129 } 130 }) 131 .width('100%') 132 .height(this.itemHeightArray[item % 100]) 133 .backgroundColor(this.colors[item % 5]) 134 }, (item: string) => item) 135 } 136 .columnsTemplate("1fr 1fr") 137 .columnsGap(10) 138 .rowsGap(5) 139 .backgroundColor(0xFAEEE0) 140 .width('100%') 141 .height('80%') 142 } 143 } 144``` 145 146In 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. 147 148 149 150## Reusing Components 151 152Now 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. 153 154During 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). 155 156```ts 157 build() { 158 Column({ space: 2 }) { 159 WaterFlow() { 160 LazyForEach(this.datasource, (item: number) => { 161 FlowItem() { 162 // Use reusable custom components. 163 ResuableFlowItem({ item: item }) 164 } 165 .onAppear(() => { 166 // Add data in advance when scrolling is about to end. 167 if (item + 20 == this.datasource.totalCount()) { 168 for (let i = 0; i < 100; i++) { 169 this.datasource.AddLastItem() 170 } 171 } 172 }) 173 .width('100%') 174 .height(this.itemHeightArray[item % 100]) 175 .backgroundColor(this.colors[item % 5]) 176 }, (item: string) => item) 177 } 178 .columnsTemplate("1fr 1fr") 179 .columnsGap(10) 180 .rowsGap(5) 181 .backgroundColor(0xFAEEE0) 182 .width('100%') 183 .height('80%') 184 } 185 } 186@Reusable 187@Component 188struct ResuableFlowItem { 189 @State item: number = 0 190 191 // 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. 192 aboutToReuse(params) { 193 this.item = params.item; 194 } 195 196 build() { 197 Column() { 198 Text("N" + this.item).fontSize(12).height('16') 199 Image('res/waterFlowTest (' + this.item % 5 + ').jpg') 200 .objectFit(ImageFit.Fill) 201 .width('100%') 202 .layoutWeight(1) 203 } 204 } 205} 206 207``` 208 209## Takeaway 210 211To 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. 212