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 ```