1# @ohos.arkui.Prefetcher (Prefetching) 2配合LazyForEach,为List、Grid、Waterfall和Swiper等容器组件滑动浏览时提供内容预加载能力,提升用户浏览体验。 3 4> **说明:** 5> 6> 本模块首批接口从API version 12开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 7 8## 导入模块 9 10```ts 11import { BasicPrefetcher, IDataSourcePrefetching, IPrefetcher } from '@kit.ArkUI'; 12``` 13 14 15## IPrefetcher 16实现此接口以提供预取能力。 17 18**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 19 20**系统能力:** SystemCapability.ArkUI.ArkUI.Full 21 22### setDataSource 23setDataSource(dataSource: IDataSourcePrefetching): void; 24 25设置支持预取的DataSource以绑定到Prefetcher。 26 27**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 28 29**系统能力:** SystemCapability.ArkUI.ArkUI.Full 30 31**参数:** 32 33| 参数名 | 类型 | 必填 | 说明 | 34|------------|---------------------------------------------------|----|------------| 35| dataSource | [IDataSourcePrefetching](#idatasourceprefetching) | 是 | 支持预取能力的数据源。 | 36 37```typescript 38class MyPrefetcher implements IPrefetcher { 39 private dataSource?: IDataSourcePrefetching; 40 41 setDataSource(dataSource: IDataSourcePrefetching): void { 42 this.dataSource = dataSource; 43 } 44 45 visibleAreaChanged(minVisible: number, maxVisible: number): void { 46 this.dataSource?.prefetch(minVisible); 47 } 48} 49``` 50 51### visibleAreaChanged 52visibleAreaChanged(minVisible: number, maxVisible: number): void; 53 54当可见区域边界发生改变时调用此方法。支持与`List`、`Grid`、`Waterfall`和`Swiper`组件配合使用。 55 56**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 57 58**系统能力:** SystemCapability.ArkUI.ArkUI.Full 59 60**参数:** 61 62| 参数名 | 类型 | 必填 | 说明 | 63|------------|--------|----|-----------| 64| minVisible | number | 是 | 列表可见区域的上界。 | 65| maxVisible | number | 是 | 列表可见区域的下界。 | 66 67```typescript 68class MyPrefetcher implements IPrefetcher { 69 private dataSource?: IDataSourcePrefetching; 70 71 setDataSource(dataSource: IDataSourcePrefetching): void { 72 this.dataSource = dataSource; 73 } 74 75 visibleAreaChanged(minVisible: number, maxVisible: number): void { 76 this.dataSource?.prefetch(minVisible); 77 } 78} 79``` 80 81## BasicPrefetcher 82BasicPrefetcher是IPrefetcher的基础实现。它提供了一种智能数据预取算法,以根据屏幕上可见区域的实时变化和预取持续时间的变化来决定应预取哪些数据项。它还可以根据用户的滚动操作来确定哪些预取请求应该被取消。 83 84**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 85 86**系统能力:** SystemCapability.ArkUI.ArkUI.Full 87 88### constructor 89constructor(dataSource?: IDataSourcePrefetching); 90 91传入支持预取的DataSource以绑定到Prefetcher。 92 93**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 94 95**系统能力:** SystemCapability.ArkUI.ArkUI.Full 96 97**参数:** 98 99| 参数名 | 类型 | 必填 | 说明 | 100|------------|---------------------------------------------------|----|------------| 101| dataSource | [IDataSourcePrefetching](#idatasourceprefetching) | 否 | 支持预取能力的数据源。 | 102 103### setDataSource 104setDataSource(dataSource: IDataSourcePrefetching): void; 105 106设置支持预取的DataSource以绑定到Prefetcher。 107 108**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 109 110**系统能力:** SystemCapability.ArkUI.ArkUI.Full 111 112**参数:** 113 114| 参数名 | 类型 | 必填 | 说明 | 115|------------|---------------------------------------------------|----|------------| 116| dataSource | [IDataSourcePrefetching](#idatasourceprefetching) | 是 | 支持预取能力的数据源。 | 117 118### visibleAreaChanged 119visibleAreaChanged(minVisible: number, maxVisible: number): void; 120 121当可见区域边界发生改变时调用此方法。支持与`List`、`Grid`、`Waterfall`和`Swiper`组件配合使用。 122 123**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 124 125**系统能力:** SystemCapability.ArkUI.ArkUI.Full 126 127**参数:** 128 129| 参数名 | 类型 | 必填 | 说明 | 130|------------|--------|----|-----------| 131| minVisible | number | 是 | 列表可见区域的上界。 | 132| maxVisible | number | 是 | 列表可见区域的下界。 | 133 134## IDataSourcePrefetching 135 136继承自[IDataSource](./arkui-ts/ts-rendering-control-lazyforeach.md#idatasource10)。实现该接口,提供具备预取能力的DataSource。 137 138**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 139 140**系统能力:** SystemCapability.ArkUI.ArkUI.Full 141 142### prefetch 143prefetch(index: number): Promise\<void\> | void; 144 145从数据集中预取指定的元素。该方法可以为同步,也可为异步。 146 147**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 148 149**系统能力:** SystemCapability.ArkUI.ArkUI.Full 150 151**参数:** 152 153| 参数名 | 类型 | 必填 | 说明 | 154|-------|--------|----|----------| 155| index | number | 是 | 预取数据项索引值。 | 156 157### cancel 158cancel?(index: number): Promise\<void\> | void; 159 160取消从数据集中预取指定的元素。该方法可以为同步,也可为异步。 161 162**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 163 164**系统能力:** SystemCapability.ArkUI.ArkUI.Full 165 166**参数:** 167 168| 参数名 | 类型 | 必填 | 说明 | 169|-------|--------|----|------------| 170| index | number | 是 | 取消预取数据项索引值。 | 171 172列表内容移出屏幕时(比如列表快速滑动场景下),预取算法判断屏幕以外的Item可以被取消预取时,该方法即会被调用。例如,如果HTTP框架支持请求取消,则可以在此处取消在prefetch中发起的网络请求。 173 174## 示例 175 176下面示例展示了Prefetcher的预加载能力。该示例采用分页的方式,配合LazyForEach实现懒加载效果,并通过添加延时来模拟加载过程。 177 178```typescript 179import { BasicPrefetcher, IDataSourcePrefetching } from '@kit.ArkUI'; 180import { image } from '@kit.ImageKit'; 181 182const ITEMS_ON_SCREEN = 8; 183 184@Entry 185@Component 186struct PrefetcherDemoComponent { 187 private page: number = 1; 188 private pageSize: number = 50; 189 private breakPoint: number = 25; 190 private readonly fetchDelayMs: number = 500; 191 private readonly dataSource = new MyDataSource(this.page, this.pageSize, this.fetchDelayMs); 192 private readonly prefetcher = new BasicPrefetcher(this.dataSource); 193 194 build() { 195 Column() { 196 List() { 197 LazyForEach(this.dataSource, (item: PictureItem, index: number) => { 198 ListItem() { 199 PictureItemComponent({ info: item }) 200 .height(`${100 / ITEMS_ON_SCREEN}%`) 201 } 202 .onAppear(() => { 203 if (index >= this.breakPoint) { 204 this.dataSource.getHttpData(++this.page, this.pageSize); 205 this.breakPoint = this.dataSource.totalCount() - this.pageSize / 2; 206 } 207 }) 208 }, (item: PictureItem) => item.title) 209 } 210 .onScrollIndex((start: number, end: number) => { 211 this.prefetcher.visibleAreaChanged(start, end); 212 }) 213 } 214 } 215} 216 217@Component 218struct PictureItemComponent { 219 @ObjectLink info: PictureItem; 220 221 build() { 222 Row() { 223 Image(this.info.imagePixelMap) 224 .objectFit(ImageFit.Contain) 225 .width('40%') 226 Text(this.info.title) 227 .width('60%') 228 } 229 } 230} 231 232@Observed 233class PictureItem { 234 readonly color: number; 235 title: string; 236 imagePixelMap: image.PixelMap | undefined; 237 key: string; 238 239 constructor(color: number, title: string) { 240 this.color = color; 241 this.title = title; 242 this.key = title; 243 } 244} 245 246type ItemIndex = number; 247type TimerId = number; 248 249class MyDataSource implements IDataSourcePrefetching { 250 private readonly items: PictureItem[]; 251 private readonly fetchDelayMs: number; 252 private readonly fetches: Map<ItemIndex, TimerId> = new Map(); 253 private readonly listeners: DataChangeListener[] = []; 254 255 constructor(pageNum: number, pageSize: number, fetchDelayMs: number) { 256 this.items = []; 257 this.fetchDelayMs = fetchDelayMs; 258 this.getHttpData(pageNum, pageSize); 259 } 260 261 async prefetch(index: number): Promise<void> { 262 const item = this.items[index]; 263 if (item.imagePixelMap) { 264 return; 265 } 266 267 // 模拟高耗时操作 268 return new Promise<void>(resolve => { 269 const timeoutId = setTimeout(async () => { 270 this.fetches.delete(index); 271 const bitmap = create10x10Bitmap(item.color); 272 const imageSource: image.ImageSource = image.createImageSource(bitmap); 273 item.imagePixelMap = await imageSource.createPixelMap(); 274 resolve(); 275 }, this.fetchDelayMs); 276 277 this.fetches.set(index, timeoutId) 278 }); 279 } 280 281 cancel(index: number): void { 282 const timerId = this.fetches.get(index); 283 if (timerId) { 284 this.fetches.delete(index); 285 clearTimeout(timerId); 286 } 287 } 288 289 // 模拟分页方式加载数据 290 getHttpData(pageNum: number, pageSize:number): void { 291 const newItems: PictureItem[] = []; 292 for (let i = (pageNum - 1) * pageSize; i < pageNum * pageSize; i++) { 293 const item = new PictureItem(getRandomColor(), `Item ${i}`); 294 newItems.push(item); 295 } 296 const startIndex = this.items.length; 297 this.items.splice(startIndex, 0, ...newItems); 298 this.notifyBatchUpdate([ 299 { 300 type: DataOperationType.ADD, 301 index: startIndex, 302 count: newItems.length, 303 key: newItems.map((item) => item.title) 304 } 305 ]); 306 } 307 308 private notifyBatchUpdate(operations: DataOperation[]) { 309 this.listeners.forEach((listener: DataChangeListener) => { 310 listener.onDatasetChange(operations); 311 }); 312 } 313 314 totalCount(): number { 315 return this.items.length; 316 } 317 318 getData(index: number): PictureItem { 319 return this.items[index]; 320 } 321 322 registerDataChangeListener(listener: DataChangeListener): void { 323 if (this.listeners.indexOf(listener) < 0) { 324 this.listeners.push(listener); 325 } 326 } 327 328 unregisterDataChangeListener(listener: DataChangeListener): void { 329 const pos = this.listeners.indexOf(listener); 330 if (pos >= 0) { 331 this.listeners.splice(pos, 1); 332 } 333 } 334} 335 336function getRandomColor(): number { 337 const maxColorCode = 256; 338 const r = Math.floor(Math.random() * maxColorCode); 339 const g = Math.floor(Math.random() * maxColorCode); 340 const b = Math.floor(Math.random() * maxColorCode); 341 342 return (r * 256 + g) * 256 + b; 343} 344 345function create10x10Bitmap(color: number): ArrayBuffer { 346 const height = 10; 347 const width = 10; 348 349 const fileHeaderLength = 14; 350 const bitmapInfoLength = 40; 351 const headerLength = fileHeaderLength + bitmapInfoLength; 352 const pixelSize = (width * 3 + 2) * height; 353 354 let length = pixelSize + headerLength; 355 356 const buffer = new ArrayBuffer(length); 357 const view16 = new Uint16Array(buffer); 358 359 view16[0] = 0x4D42; 360 view16[1] = length & 0xffff; 361 view16[2] = length >> 16; 362 view16[5] = headerLength; 363 364 let offset = 7; 365 view16[offset++] = bitmapInfoLength & 0xffff; 366 view16[offset++] = bitmapInfoLength >> 16; 367 view16[offset++] = width & 0xffff; 368 view16[offset++] = width >> 16; 369 view16[offset++] = height & 0xffff; 370 view16[offset++] = height >> 16; 371 view16[offset++] = 1; 372 view16[offset++] = 24; 373 374 const b = color & 0xff; 375 const g = (color >> 8) & 0xff; 376 const r = color >> 16; 377 offset = headerLength; 378 const view8 = new Uint8Array(buffer); 379 for (let y = 0; y < height; y++) { 380 for (let x = 0; x < width; x++) { 381 view8[offset++] = b; 382 view8[offset++] = g; 383 view8[offset++] = r; 384 } 385 offset += 2; 386 } 387 388 return buffer; 389} 390``` 391 392演示效果如下: 393 394 395 396## 补充说明 397 398开发者也可使用OpenHarmony三方库[@netteam/prefetcher](https://ohpm.openharmony.cn/#/cn/detail/@netteam%2Fprefetcher)开发预加载功能。该三方库提供了更多的接口,可以更加便捷有效地实现数据预加载。