• 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
27    export function getImgFromDB() {
28      // Simulate the operation of querying the database and returning data.
29      let task = new taskpool.Task(query);
30      task.onReceiveData(fillImg);
31      taskpool.execute(task);
32    }
33    ```
34    <!-- @[query_database_return_main_thread](https://gitee.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/ArkTsConcurrent/ApplicationMultithreadingDevelopment/PracticalCases/entry/src/main/ets/managers/Mock.ets) -->
35
362. Encapsulate a waterfall data source for loading data to the **WaterFlow** component.
37
38    ```ts
39    // WaterFlowDataSource.ets
40
41    // An object that implements the IDataSource interface, which is used by the WaterFlow component to load data.
42    export class WaterFlowDataSource implements IDataSource {
43      private dataArray: number[] = [];
44      private listeners: DataChangeListener[] = [];
45
46      constructor() {
47        for (let i = 0; i < 100; i++) {
48          this.dataArray.push(i);
49        }
50      }
51
52      // Obtain the data corresponding to the specified index.
53      public getData(index: number): number {
54        return this.dataArray[index];
55      }
56
57      // Notify the controller that data has been reloaded.
58      notifyDataReload(): void {
59        this.listeners.forEach(listener => {
60          listener.onDataReloaded();
61        })
62      }
63
64      // Notify the controller that data has been added.
65      notifyDataAdd(index: number): void {
66        this.listeners.forEach(listener => {
67          listener.onDataAdd(index);
68        })
69      }
70
71      // Notify the controller that data has changed.
72      notifyDataChange(index: number): void {
73        this.listeners.forEach(listener => {
74          listener.onDataChange(index);
75        })
76      }
77
78      // Notify the controller that data has been deleted.
79      notifyDataDelete(index: number): void {
80        this.listeners.forEach(listener => {
81          listener.onDataDelete(index);
82        })
83      }
84
85      // Notify the controller that the data position has changed.
86      notifyDataMove(from: number, to: number): void {
87        this.listeners.forEach(listener => {
88          listener.onDataMove(from, to);
89        })
90      }
91
92      //Notify the controller that data has been deleted in batch.
93      notifyDatasetChange(operations: DataOperation[]): void {
94        this.listeners.forEach(listener => {
95          listener.onDatasetChange(operations);
96        })
97      }
98
99      // Obtain the total number of data records.
100      public totalCount(): number {
101        return this.dataArray.length;
102      }
103
104      // Register a controller for data changes.
105      registerDataChangeListener(listener: DataChangeListener): void {
106        if (this.listeners.indexOf(listener) < 0) {
107          this.listeners.push(listener);
108        }
109      }
110
111      // Unregister the controller for data changes.
112      unregisterDataChangeListener(listener: DataChangeListener): void {
113        const pos = this.listeners.indexOf(listener);
114        if (pos >= 0) {
115          this.listeners.splice(pos, 1);
116        }
117      }
118
119      // Add data.
120      public add1stItem(): void {
121        this.dataArray.splice(0, 0, this.dataArray.length);
122        this.notifyDataAdd(0);
123      }
124
125      // Add an element to the end of the data.
126      public addLastItem(): void {
127        this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length);
128        this.notifyDataAdd(this.dataArray.length - 1);
129      }
130
131      // Add an element at the specified index.
132      public addItem(index: number): void {
133        this.dataArray.splice(index, 0, this.dataArray.length);
134        this.notifyDataAdd(index);
135      }
136
137      // Delete the first element.
138      public delete1stItem(): void {
139        this.dataArray.splice(0, 1);
140        this.notifyDataDelete(0);
141      }
142
143      // Delete the second element.
144      public delete2ndItem(): void {
145        this.dataArray.splice(1, 1);
146        this.notifyDataDelete(1);
147      }
148
149      // Delete the last element.
150      public deleteLastItem(): void {
151        this.dataArray.splice(-1, 1);
152        this.notifyDataDelete(this.dataArray.length);
153      }
154
155      // Delete an element at the specified index.
156      public deleteItem(index: number): void {
157        this.dataArray.splice(index, 1);
158        this.notifyDataDelete(index);
159      }
160
161      // Reload the data.
162      public reload(): void {
163        this.dataArray.splice(1, 1);
164        this.dataArray.splice(3, 2);
165        this.notifyDataReload();
166      }
167    }
168    ```
169    <!-- @[encapsulate_waterfall_data_source](https://gitee.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/ArkTsConcurrent/ApplicationMultithreadingDevelopment/PracticalCases/entry/src/main/ets/managers/WaterFlowDataSource.ets) -->
170
1713. 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.
172
173    ```ts
174    // Index.ets
175    import { WaterFlowDataSource } from './WaterFlowDataSource';
176    import { getImgFromDB } from './Mock';
177
178    // Simulate an image array.
179    let img = new Array<string>(33);
180    export function fillImg(imgArr : Array<string>) {
181      img = imgArr;
182    }
183
184    @Entry
185    @Component
186    struct WaterFlowDemo {
187      @State minSize: number = 80;
188      @State maxSize: number = 180;
189      @State fontSize: number = 24;
190      @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F];
191      scroller: Scroller = new Scroller();
192      dataSource: WaterFlowDataSource = new WaterFlowDataSource();
193      private itemWidthArray: number[] = [];
194      private itemHeightArray: number[] = [];
195      // Calculate the width and height of a flow item.
196      getSize() {
197        let ret = Math.floor(Math.random() * this.maxSize);
198        return (ret > this.minSize ? ret : this.minSize);
199      }
200
201      // Set the width and height array of the flow item.
202      setItemSizeArray() {
203        for (let i = 0; i < 100; i++) {
204          this.itemWidthArray.push(this.getSize());
205          this.itemHeightArray.push(this.getSize());
206        }
207      }
208
209      aboutToAppear() {
210        this.setItemSizeArray();
211      }
212
213      @Builder
214      itemFoot() {
215        Column() {
216          Text(`Footer`)
217            .fontSize(10)
218            .backgroundColor(Color.Red)
219            .width(50)
220            .height(50)
221            .align(Alignment.Center)
222            .margin({ top: 2 });
223        }
224      }
225
226      build() {
227        Column({ space: 2 }) {
228          Text("ArkUI WaterFlow Demo")
229            .onAppear(()=>{
230              getImgFromDB();
231            })
232          WaterFlow() {
233            LazyForEach(this.dataSource, (item: number) => {
234              FlowItem() {
235                Column() {
236                  Text("N" + item)
237                    .fontSize(12)
238                    .height('16')
239                    .onClick(()=>{
240
241                    });
242                  // 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)
243                  if (img[item % 33] == null) {
244                    Text('Loading image...')
245                      .width('100%')
246                      .layoutWeight(1);
247                  }
248                  Text(img[item % 33])
249                    .width('100%')
250                    .layoutWeight(1);
251                }
252              }
253              .onAppear(() => {
254                // Pre-load more data when approaching the bottom.
255                if (item + 20 == this.dataSource.totalCount()) {
256                  for (let i = 0; i < 100; i++) {
257                    this.dataSource.addLastItem();
258                  }
259                }
260              })
261              .width('100%')
262              .height(this.itemHeightArray[item % 100])
263              .backgroundColor(this.colors[item % 5])
264            }, (item: string) => item)
265          }
266          .columnsTemplate("1fr 1fr")
267          .columnsGap(10)
268          .rowsGap(5)
269          .backgroundColor(0xFAEEE0)
270          .width('100%')
271          .height('100%')
272          .onReachStart(() => {
273            console.info('TaskPoolTest-waterFlow reach start');
274          })
275          .onScrollStart(() => {
276            console.info('TaskPoolTest-waterFlow scroll start');
277          })
278          .onScrollStop(() => {
279            console.info('TaskPoolTest-waterFlow scroll stop');
280          })
281          .onScrollFrameBegin((offset: number, state: ScrollState) => {
282            console.info('TaskPoolTest-waterFlow scrollFrameBegin offset: ' + offset + ' state: ' + state.toString());
283            return { offsetRemain: offset };
284          })
285        }
286      }
287    }
288    ```
289    <!-- @[receive_child_thread_data_render_waterfall_component](https://gitee.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/ArkTsConcurrent/ApplicationMultithreadingDevelopment/PracticalCases/entry/src/main/ets/managers/WaterfallRendering.ets) -->
290