1# ArkUI瀑布流渲染场景 2<!--Kit: ArkTS--> 3<!--Subsystem: CommonLibrary--> 4<!--Owner: @lijiamin2025--> 5<!--Designer: @weng-changcheng--> 6<!--Tester: @kirl75; @zsw_zhushiwei--> 7<!--Adviser: @ge-yafang--> 8 9此处提供使用任务池[TaskPool](../reference/apis-arkts/js-apis-taskpool.md)提升[WaterFlow瀑布流](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md)渲染性能的开发指导。UI线程查询数据库数据,并将数据渲染到瀑布流组件,数据过大时会导致UI线程长时间等待,影响用户体验。因此,我们可以将数据查询操作放到子线程中,并通过TaskPool的接口返回数据给UI线程。 10 11本示例说明以下场景: 12- 模拟子线程[读取数据库数据](batch-database-operations-guide.md)并返回给UI线程。 13- UI线程感知到数据更新,将子线程返回的数据渲染到瀑布流组件。 14 151. 定义一个接口,用于子线程查询数据库并将数据返回给UI线程。 16 17 ```ts 18 // Mock.ets 19 import { taskpool } from '@kit.ArkTS'; 20 import { fillImg } from './Index'; 21 22 @Concurrent 23 function query() { 24 console.info("TaskPoolTest-this is query"); 25 let result = new Array<string>(33); 26 for (let i = 0; i < 33; i++) { 27 result[i] = 'Image' + i; 28 } 29 taskpool.Task.sendData(result); 30 } 31 32 33 export function getImgFromDB() { 34 //此处模拟查询数据库,并返回数据 35 let task = new taskpool.Task(query); 36 task.onReceiveData(fillImg); 37 taskpool.execute(task); 38 } 39 ``` 40 <!-- @[query_database_return_main_thread](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/ArkTsConcurrent/ApplicationMultithreadingDevelopment/PracticalCases/entry/src/main/ets/managers/Mock.ets) --> 41 422. 封装一个瀑布流组件数据源,用于瀑布流组件加载数据。 43 44 ```ts 45 // WaterFlowDataSource.ets 46 47 // 实现IDataSource接口的对象,用于瀑布流组件加载数据 48 export class WaterFlowDataSource implements IDataSource { 49 private dataArray: number[] = []; 50 private listeners: DataChangeListener[] = []; 51 52 constructor() { 53 for (let i = 0; i < 100; i++) { 54 this.dataArray.push(i); 55 } 56 } 57 58 // 获取索引对应的数据 59 public getData(index: number): number { 60 return this.dataArray[index]; 61 } 62 63 // 通知控制器数据重新加载 64 notifyDataReload(): void { 65 this.listeners.forEach(listener => { 66 listener.onDataReloaded(); 67 }) 68 } 69 70 // 通知控制器数据增加 71 notifyDataAdd(index: number): void { 72 this.listeners.forEach(listener => { 73 listener.onDataAdd(index); 74 }) 75 } 76 77 // 通知控制器数据变化 78 notifyDataChange(index: number): void { 79 this.listeners.forEach(listener => { 80 listener.onDataChange(index); 81 }) 82 } 83 84 // 通知控制器数据删除 85 notifyDataDelete(index: number): void { 86 this.listeners.forEach(listener => { 87 listener.onDataDelete(index); 88 }) 89 } 90 91 // 通知控制器数据位置变化 92 notifyDataMove(from: number, to: number): void { 93 this.listeners.forEach(listener => { 94 listener.onDataMove(from, to); 95 }) 96 } 97 98 //通知控制器数据批量修改 99 notifyDatasetChange(operations: DataOperation[]): void { 100 this.listeners.forEach(listener => { 101 listener.onDatasetChange(operations); 102 }) 103 } 104 105 // 获取数据总数 106 public totalCount(): number { 107 return this.dataArray.length; 108 } 109 110 // 注册改变数据的控制器 111 registerDataChangeListener(listener: DataChangeListener): void { 112 if (this.listeners.indexOf(listener) < 0) { 113 this.listeners.push(listener); 114 } 115 } 116 117 // 注销改变数据的控制器 118 unregisterDataChangeListener(listener: DataChangeListener): void { 119 const pos = this.listeners.indexOf(listener); 120 if (pos >= 0) { 121 this.listeners.splice(pos, 1); 122 } 123 } 124 125 // 增加数据 126 public add1stItem(): void { 127 this.dataArray.splice(0, 0, this.dataArray.length); 128 this.notifyDataAdd(0); 129 } 130 131 // 在数据尾部增加一个元素 132 public addLastItem(): void { 133 this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length); 134 this.notifyDataAdd(this.dataArray.length - 1); 135 } 136 137 // 在指定索引位置增加一个元素 138 public addItem(index: number): void { 139 this.dataArray.splice(index, 0, this.dataArray.length); 140 this.notifyDataAdd(index); 141 } 142 143 // 删除第一个元素 144 public delete1stItem(): void { 145 this.dataArray.splice(0, 1); 146 this.notifyDataDelete(0); 147 } 148 149 // 删除第二个元素 150 public delete2ndItem(): void { 151 this.dataArray.splice(1, 1); 152 this.notifyDataDelete(1); 153 } 154 155 // 删除最后一个元素 156 public deleteLastItem(): void { 157 this.dataArray.splice(-1, 1); 158 this.notifyDataDelete(this.dataArray.length - 1); 159 } 160 161 // 在指定索引位置删除一个元素 162 public deleteItem(index: number): void { 163 this.dataArray.splice(index, 1); 164 this.notifyDataDelete(index); 165 } 166 167 // 重新加载数据 168 public reload(): void { 169 this.dataArray.splice(1, 1); 170 this.dataArray.splice(3, 2); 171 this.notifyDataReload(); 172 } 173 } 174 ``` 175 <!-- @[encapsulate_waterfall_data_source](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/ArkTsConcurrent/ApplicationMultithreadingDevelopment/PracticalCases/entry/src/main/ets/managers/WaterFlowDataSource.ets) --> 176 1773. 在应用冷启动阶段,调用`getImgFromDB()`接口,将数据查询操作放到子线程中。在`img`接收到子线程返回的数据后,将数据渲染到瀑布流组件。 178 179 ```ts 180 // Index.ets 181 import { WaterFlowDataSource } from './WaterFlowDataSource'; 182 import { getImgFromDB } from './Mock'; 183 184 // 模拟图片数组 185 let img = new Array<string>(33); 186 export function fillImg(imgArr : Array<string>) { 187 img = imgArr; 188 } 189 190 @Entry 191 @Component 192 struct WaterFlowDemo { 193 @State minSize: number = 80; 194 @State maxSize: number = 180; 195 @State fontSize: number = 24; 196 @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]; 197 scroller: Scroller = new Scroller(); 198 dataSource: WaterFlowDataSource = new WaterFlowDataSource(); 199 private itemWidthArray: number[] = []; 200 private itemHeightArray: number[] = []; 201 // 计算FlowItem宽/高 202 getSize() { 203 let ret = Math.floor(Math.random() * this.maxSize); 204 return (ret > this.minSize ? ret : this.minSize); 205 } 206 207 // 设置FlowItem的宽/高数组 208 setItemSizeArray() { 209 for (let i = 0; i < 100; i++) { 210 this.itemWidthArray.push(this.getSize()); 211 this.itemHeightArray.push(this.getSize()); 212 } 213 } 214 215 aboutToAppear() { 216 this.setItemSizeArray(); 217 } 218 219 @Builder 220 itemFoot() { 221 Column() { 222 Text(`Footer`) 223 .fontSize(10) 224 .backgroundColor(Color.Red) 225 .width(50) 226 .height(50) 227 .align(Alignment.Center) 228 .margin({ top: 2 }); 229 } 230 } 231 232 build() { 233 Column({ space: 2 }) { 234 Text("ArkUI WaterFlow Demo") 235 .onAppear(()=>{ 236 getImgFromDB(); 237 }) 238 WaterFlow() { 239 LazyForEach(this.dataSource, (item: number) => { 240 FlowItem() { 241 Column() { 242 Text("N" + item) 243 .fontSize(12) 244 .height('16') 245 .onClick(()=>{ 246 247 }); 248 // 为了模拟图片加载,使用Text组件显示,正常加载jpg文件时,可以直接使用Image组件,参考Image(this.img[item % 33]).objectFit(ImageFit.Contain).width('100%').layoutWeight(1) 249 if (img[item % 33] == null) { 250 Text("图片加载中...") 251 .width('100%') 252 .layoutWeight(1); 253 } 254 Text(img[item % 33]) 255 .width('100%') 256 .layoutWeight(1); 257 } 258 } 259 .onAppear(() => { 260 // 即将触底时提前增加数据 261 if (item + 20 == this.dataSource.totalCount()) { 262 for (let i = 0; i < 100; i++) { 263 this.dataSource.addLastItem(); 264 } 265 } 266 }) 267 .width('100%') 268 .height(this.itemHeightArray[item % 100]) 269 .backgroundColor(this.colors[item % 5]) 270 }, (item: string) => item) 271 } 272 .columnsTemplate("1fr 1fr") 273 .columnsGap(10) 274 .rowsGap(5) 275 .backgroundColor(0xFAEEE0) 276 .width('100%') 277 .height('100%') 278 .onReachStart(() => { 279 console.info('TaskPoolTest-waterFlow reach start'); 280 }) 281 .onScrollStart(() => { 282 console.info('TaskPoolTest-waterFlow scroll start'); 283 }) 284 .onScrollStop(() => { 285 console.info('TaskPoolTest-waterFlow scroll stop'); 286 }) 287 .onScrollFrameBegin((offset: number, state: ScrollState) => { 288 console.info('TaskPoolTest-waterFlow scrollFrameBegin offset: ' + offset + ' state: ' + state.toString()); 289 return { offsetRemain: offset }; 290 }) 291 } 292 } 293 } 294 ``` 295 <!-- @[receive_child_thread_data_render_waterfall_component](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/ArkTsConcurrent/ApplicationMultithreadingDevelopment/PracticalCases/entry/src/main/ets/managers/WaterfallRendering.ets) -->