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![](figures/waterflow-perf-demo1.gif)
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![](figures/waterflow-perf-demo2.gif)
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