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