• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# ArkUI Waterfall Rendering
2
3This topic describes how to use [TaskPool](../reference/apis-arkts/js-apis-taskpool.md) to improve the rendering performance of the [WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md) component. When the UI thread queries large volumes of data from the database and renders the data to the **WaterFlow** component, the UI may become unresponsive, negatively impacting user experience. To address this, we can offload data query to child threads and return the data to the UI thread using TaskPool APIs.
4
5This example describes the following scenarios:
6- A child thread [queries data from the database](batch-database-operations-guide.md) and returns the data to the UI thread.
7- The UI thread detects data updates and renders the data returned from the child thread to the **WaterFlow** component.
8
91. Define an interface for the child thread to query data from the database and return the data to the UI thread.
10
11    ```ts
12    // Mock.ets
13    import { taskpool } from '@kit.ArkTS';
14    import { fillImg } from './Index';
15
16    @Concurrent
17    function query() {
18      console.info("TaskPoolTest-this is query");
19      let result = new Array<string>(33);
20      for (let i = 0; i < 33; i++) {
21        result[i] = 'Image' + i;
22      }
23      taskpool.Task.sendData(result);
24    }
25
26        export function getImgFromDB() {
27          // Simulate the operation of querying the database and returning data.
28          let task = new taskpool.Task(query);
29          task.onReceiveData(fillImg);
30          taskpool.execute(task);
31        }
32    ```
33
342. Encapsulate a waterfall data source for loading data to the **WaterFlow** component.
35
36    ```ts
37    // WaterFlowDataSource.ets
38
39    // An object that implements the IDataSource interface, which is used by the WaterFlow component to load data.
40    export class WaterFlowDataSource implements IDataSource {
41      private dataArray: number[] = [];
42      private listeners: DataChangeListener[] = [];
43
44      constructor() {
45        for (let i = 0; i < 100; i++) {
46          this.dataArray.push(i);
47        }
48      }
49
50      // Obtain the data corresponding to the specified index.
51      public getData(index: number): number {
52        return this.dataArray[index];
53      }
54
55      // Notify the controller that data has been reloaded.
56      notifyDataReload(): void {
57        this.listeners.forEach(listener => {
58          listener.onDataReloaded();
59        })
60      }
61
62      // Notify the controller that data has been added.
63      notifyDataAdd(index: number): void {
64        this.listeners.forEach(listener => {
65          listener.onDataAdd(index);
66        })
67      }
68
69      // Notify the controller that data has changed.
70      notifyDataChange(index: number): void {
71        this.listeners.forEach(listener => {
72          listener.onDataChange(index);
73        })
74      }
75
76      // Notify the controller that data has been deleted.
77      notifyDataDelete(index: number): void {
78        this.listeners.forEach(listener => {
79          listener.onDataDelete(index);
80        })
81      }
82
83      // Notify the controller that the data position has changed.
84      notifyDataMove(from: number, to: number): void {
85        this.listeners.forEach(listener => {
86          listener.onDataMove(from, to);
87        })
88      }
89
90      //Notify the controller that data has been deleted in batch.
91      notifyDatasetChange(operations: DataOperation[]): void {
92        this.listeners.forEach(listener => {
93          listener.onDatasetChange(operations);
94        })
95      }
96
97      // Obtain the total number of data records.
98      public totalCount(): number {
99        return this.dataArray.length;
100      }
101
102      // Register a controller for data changes.
103      registerDataChangeListener(listener: DataChangeListener): void {
104        if (this.listeners.indexOf(listener) < 0) {
105          this.listeners.push(listener);
106        }
107      }
108
109      // Unregister the controller for data changes.
110      unregisterDataChangeListener(listener: DataChangeListener): void {
111        const pos = this.listeners.indexOf(listener);
112        if (pos >= 0) {
113          this.listeners.splice(pos, 1);
114        }
115      }
116
117      // Add data.
118      public add1stItem(): void {
119        this.dataArray.splice(0, 0, this.dataArray.length);
120        this.notifyDataAdd(0);
121      }
122
123      // Add an element to the end of the data.
124      public addLastItem(): void {
125        this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length);
126        this.notifyDataAdd(this.dataArray.length - 1);
127      }
128
129      // Add an element at the specified index.
130      public addItem(index: number): void {
131        this.dataArray.splice(index, 0, this.dataArray.length);
132        this.notifyDataAdd(index);
133      }
134
135      // Delete the first element.
136      public delete1stItem(): void {
137        this.dataArray.splice(0, 1);
138        this.notifyDataDelete(0);
139      }
140
141      // Delete the second element.
142      public delete2ndItem(): void {
143        this.dataArray.splice(1, 1);
144        this.notifyDataDelete(1);
145      }
146
147      // Delete the last element.
148      public deleteLastItem(): void {
149        this.dataArray.splice(-1, 1);
150        this.notifyDataDelete(this.dataArray.length);
151      }
152
153      // Delete an element at the specified index.
154      public deleteItem(index: number): void {
155        this.dataArray.splice(index, 1);
156        this.notifyDataDelete(index);
157      }
158
159      // Reload the data.
160      public reload(): void {
161        this.dataArray.splice(1, 1);
162        this.dataArray.splice(3, 2);
163        this.notifyDataReload();
164      }
165    }
166    ```
167
1683. During cold start of the application, call the **getImgFromDB()** interface to offload the data query operation to a child thread. After the img receives data from the child thread, render the data to the **WaterFlow** component.
169
170    ```ts
171    // Index.ets
172    import { WaterFlowDataSource } from './WaterFlowDataSource';
173    import { getImgFromDB } from './Mock';
174
175    // Simulate an image array.
176    let img = new Array<string>(33);
177    export function fillImg(imgArr : Array<string>) {
178      img = imgArr;
179    }
180
181    @Entry
182    @Component
183    struct WaterFlowDemo {
184      @State minSize: number = 80;
185      @State maxSize: number = 180;
186      @State fontSize: number = 24;
187      @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F];
188      scroller: Scroller = new Scroller();
189      dataSource: WaterFlowDataSource = new WaterFlowDataSource();
190      private itemWidthArray: number[] = [];
191      private itemHeightArray: number[] = [];
192      // Calculate the width and height of a flow item.
193      getSize() {
194        let ret = Math.floor(Math.random() * this.maxSize);
195        return (ret > this.minSize ? ret : this.minSize);
196      }
197
198      // Set the width and height array of the flow item.
199      setItemSizeArray() {
200        for (let i = 0; i < 100; i++) {
201          this.itemWidthArray.push(this.getSize());
202          this.itemHeightArray.push(this.getSize());
203        }
204      }
205
206      aboutToAppear() {
207        this.setItemSizeArray();
208      }
209
210      @Builder
211      itemFoot() {
212        Column() {
213          Text(`Footer`)
214            .fontSize(10)
215            .backgroundColor(Color.Red)
216            .width(50)
217            .height(50)
218            .align(Alignment.Center)
219            .margin({ top: 2 });
220        }
221      }
222
223      build() {
224        Column({ space: 2 }) {
225          Text("ArkUI WaterFlow Demo")
226            .onAppear(()=>{
227              getImgFromDB();
228            })
229          WaterFlow() {
230            LazyForEach(this.dataSource, (item: number) => {
231              FlowItem() {
232                Column() {
233                  Text("N" + item)
234                    .fontSize(12)
235                    .height('16')
236                    .onClick(()=>{
237
238                    });
239                  // To simulate image loading, use the Text component. For actual JPG loading, use the Image component directly. Example: Image(this.img[item % 33]).objectFit(ImageFit.Contain).width('100%').layoutWeight(1)
240                  if (img[item % 33] == null) {
241                    Text('Loading image...')
242                      .width('100%')
243                      .layoutWeight(1);
244                  }
245                  Text(img[item % 33])
246                    .width('100%')
247                    .layoutWeight(1);
248                }
249              }
250              .onAppear(() => {
251                // Pre-load more data when approaching the bottom.
252                if (item + 20 == this.dataSource.totalCount()) {
253                  for (let i = 0; i < 100; i++) {
254                    this.dataSource.addLastItem();
255                  }
256                }
257              })
258              .width('100%')
259              .height(this.itemHeightArray[item % 100])
260              .backgroundColor(this.colors[item % 5])
261            }, (item: string) => item)
262          }
263          .columnsTemplate("1fr 1fr")
264          .columnsGap(10)
265          .rowsGap(5)
266          .backgroundColor(0xFAEEE0)
267          .width('100%')
268          .height('100%')
269          .onReachStart(() => {
270            console.info('TaskPoolTest-waterFlow reach start');
271          })
272          .onScrollStart(() => {
273            console.info('TaskPoolTest-waterFlow scroll start');
274          })
275          .onScrollStop(() => {
276            console.info('TaskPoolTest-waterFlow scroll stop');
277          })
278          .onScrollFrameBegin((offset: number, state: ScrollState) => {
279            console.info('TaskPoolTest-waterFlow scrollFrameBegin offset: ' + offset + ' state: ' + state.toString());
280            return { offsetRemain: offset };
281          })
282        }
283      }
284    }
285    ```