• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![Prefetcher-Demo](./figures/prefetcher-demo.gif)
395
396## 补充说明
397
398开发者也可使用OpenHarmony三方库[@netteam/prefetcher](https://ohpm.openharmony.cn/#/cn/detail/@netteam%2Fprefetcher)开发预加载功能。该三方库提供了更多的接口,可以更加便捷有效地实现数据预加载。