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