1# High-Performance Swiper Development
2
3## Background
4
5During application development, the **Swiper ** component is frequently seen in page turning scenarios, as in home screen and gallery applications. In general cases, the on-demand loading technique is adopted for the **Swiper ** component. This means that, a new page is loaded and rendered only when it is about to be displayed.This process includes the following steps:
6
7- If the target page uses a custom component decorated by @Component, the build function of the custom component is executed and the internal UI component is created.
8
9- If [LazyForEach](../quick-start/arkts-rendering-control-lazyforeach.md) is used, the UI generation function of LazyForEach is executed to generate UI components.
10
11- After the UI components are built, their layout is calculated and drawn.
12
13In complex page scenarios, the preceding process may last for a long time. As a result, frame freezing occurs during swiping, undermining the swiping experience and even becoming a performance bottleneck of the entire application. For example, in terms of browsing large images in the Gallery application, if an image is not loaded until the first frame of swiping, the first frame will take a long time or even frame loss will occur, slowing down the application performance.
14
15This is where the pre-loading mechanism of the **Swiper ** component comes into picture. By allowing for prebuilding and prerendering components in the idle time of the main thread, this mechanism can greatly improve the swiping experience.
16
17## When to Use
18
19The pre-loading mechanism of the **Swiper ** component is preferred where time-consuming loading is involved, especially in the following scenarios:
20
21- The number of child components in **Swiper ** is greater than or equal to 5.
22
23- The child components in **Swiper ** exhibit complex animations.
24
25- Loading child components in **Swiper ** is accompanied by time-consuming operations such as network requests.
26
27- The child components in **Swiper ** contain a large number of images or resources to be rendered.
28
29## Understanding Pre-loading of Swiper
30
31The pre-loading mechanism of **Swiper ** is designed to offer an uninterrupted swiping experience, by allowing **Swiper ** to load page content before the associated page is actually displayed As opposed to the traditional approach, where page loading happens concurrently with processing of the swipe event, pre-loading is triggered when the animation for finger lifting starts. As the animation for finger lifting is calculated is in the rendering thread, the main thread has time to perform pre-loading. To improve swipe performance with less memory usage, combine pre-loading with the on-demand loading and destruction capabilities of **LazyForEach**.
32
33## How to Use
34
35- Set the number of elements to load ahead of time through the [cachedCount](../reference/apis-arkui/arkui-ts/ts-container-swiper.md#attributes) attribute of **Swiper **.
36
37Below shows the pre-loading result of the **Swiper ** component that contains five pages and its **cacheCount** attribute set to **1** and **loop** attribute set to **false**.
38 ![loop=false](figures/swiper_loop_false.png)
39
40\
41 Below shows the pre-loading result of the **Swiper ** component that contains five pages and its **cacheCount** attribute set to **1** and **loop** attribute set to **true**.
42 ![loop=true](figures/swiper_loop_true.png)
43
44- [LazyForEach](../quick-start/arkts-rendering-control-lazyforeach.md) is used to dynamically load and destroy the child components in **Swiper **.
45
46**Example**
47
48```TypeScript
49class MyDataSource implements IDataSource { // LazyForEach data source
50  private list: number[] = [];
51
52  constructor(list: number[]) {
53    this.list = list;
54  }
55
56  totalCount(): number {
57    return this.list.length;
58  }
59
60  getData(index: number): number {
61    return this.list[index];
62  }
63
64  registerDataChangeListener(_: DataChangeListener): void {
65  }
66
67  unregisterDataChangeListener(): void {
68  }
69}
70
71@Component
72struct SwiperChildPage { // Child component in <Swiper>
73  @State arr: number[] = [];
74
75  aboutToAppear(): void {
76    for (let i = 1; i <= 100; i++) {
77      this.arr.push(i);
78    }
79  }
80
81  build() {
82    Column() {
83      List({ space: 20 }) {
84        ForEach(this.arr, (index: number) => {
85          ListItem() {
86            Text(index.toString())
87              .height('4.5%')
88              .fontSize(16)
89              .textAlign(TextAlign.Center)
90              .backgroundColor(0xFFFFFF)
91          }
92          .border({ width: 2, color: Color.Green })
93        }, (index: number) => index.toString());
94      }
95      .height("95%")
96      .width("95%")
97      .border({ width: 3, color: Color.Red })
98      .lanes({ minLength: 40, maxLength: 40 })
99      .alignListItem(ListItemAlign.Start)
100      .scrollBar(BarState.Off)
101
102    }.width('100%').height('100%').padding({ top: 5 });
103  }
104}
105
106@Entry
107@Preview
108@Component
109struct SwiperExample {
110  private dataSrc: MyDataSource = new MyDataSource([]);
111
112  aboutToAppear(): void {
113    let list: Array<number> = []
114    for (let i = 1; i <= 10; i++) {
115      list.push(i);
116    }
117    this.dataSrc = new MyDataSource(list);
118  }
119
120  build() {
121    Column({ space: 5 }) {
122      Swiper() {
123        LazyForEach(this.dataSrc, (_: number) => {
124          SwiperChildPage();
125        }, (item: number) => item.toString());
126      }
127      .loop(false)
128      .cachedCount(1) // Load one child component ahead of time.
129      .indicator(true)
130      .duration(100)
131      .displayArrow({
132        showBackground: true,
133        isSidebarMiddle: true,
134        backgroundSize: 40,
135        backgroundColor: Color.Orange,
136        arrowSize: 25,
137        arrowColor: Color.Black
138      }, false)
139      .curve(Curve.Linear)
140
141    }.width('100%')
142    .margin({ top: 5 })
143  }
144}
145
146```
147
148## Verification
149
150To better demonstrate the performance improvements brought by the **Swiper ** component's pre-loading mechanism, the following prerequisites are used:
151
152- The **Swiper ** component contains 10 **List** components.
153
154- Each of the **List** component contains 100 list items.
155
156Under these prerequisites, using the **Swiper ** component's pre-loading mechanism can save about 40% time for each page turning action and ensure that no frame is lost during page turning.
157
158## Optimization Suggestion
159
160Component building and layout calculation take some time. Therefore, the value of **cachedCount** cannot be as large as possible. If the value of **cachedCount** is too large, the application performance may deteriorate. Currently, the duration of the animation for finger lifting is about 400 ms for the **Swiper ** component. If the time for the application to load a child component ranges from 100 ms to 200 ms, you are advised to set **cachedCount** to **1** or **2**, so that the pre-loading can be completed before the animation ends.
161
162In light of this, the [OnAnimationStart](../reference/apis-arkui/arkui-ts/ts-container-swiper.md#events) callback provided by **Swiper ** can be used, which is called when the switching animation starts. At this time, the main thread is idle, and the application can make full use of this period to preload resources such as images, reducing the time required for preloading nodes specified by **cachedCount**.
163
164**Example**
165
166The page code of the child component in **Swiper ** is as follows.
167
168When the child component is built for the first time (the lifecycle reaches [aboutToAppear](../quick-start/arkts-page-custom-components-lifecycle.md)), the system checks the data source for data that matches the index. If no data is found, the system loads resources before building the node. If data is found, the system directly builds the node.
169
170```TypeScript
171import image from '@ohos.multimedia.image';
172import { MyDataSource } from './Index'
173
174@Component
175export struct PhotoItem { // Child component in <Swiper>
176  myIndex: number = 0;
177  private dataSource: MyDataSource = new MyDataSource([]);
178  context = getContext(this);
179  @State imageContent: image.PixelMap | undefined = undefined;
180
181  aboutToAppear(): void {
182    console.info(`aboutToAppear` + this.myIndex);
183    this.imageContent = this.dataSource.getData(this.myIndex)?.image;
184    if (!this.imageContent) {// Check the data source for data that matches the index. If no data is found, the system loads resources before building the node.
185      try {
186        // Obtain a resource manager.
187        const resourceMgr = this.context.resourceManager;
188        // Obtain the ArrayBuffer instance of item.jpg in the rawfile folder.
189        let str = "item" + (this.myIndex + 1) + ".jpg";
190        resourceMgr.getRawFileContent(str).then((value) => {
191          // Create an ImageSource instance.
192          const imageSource = image.createImageSource(value.buffer);
193          imageSource.createPixelMap().then((value) => {
194            console.log("aboutToAppear push" + this.myIndex)
195            this.dataSource.addData(this.myIndex, { description: "" + this.myIndex, image: value })
196            this.imageContent = value;
197          })
198        })
199      } catch (err) {
200        console.log("error code" + err);
201      }
202    }
203  }
204
205  build() {
206    Column() {
207      Image(this.imageContent)
208        .width("100%")
209        .height("100%")
210    }
211  }
212}
213```
214
215The code of the Swiper  home page is as follows:
216```
217import Curves from '@ohos.curves';
218import { PhotoItem } from './PhotoItem'
219import image from '@ohos.multimedia.image';
220
221interface MyObject {
222  description: string,
223  image: image.PixelMap,
224};
225
226export class MyDataSource implements IDataSource {
227  private list: MyObject[] = []
228
229  constructor(list: MyObject[]) {
230    this.list = list
231  }
232
233  totalCount(): number {
234    return this.list.length
235  }
236
237  getData(index: number): MyObject {
238    return this.list[index]
239  }
240
241  registerDataChangeListener(listener: DataChangeListener): void {
242  }
243
244  unregisterDataChangeListener(listener: DataChangeListener): void {
245  }
246
247  addData(index: number, data: MyObject) {
248    this.list[index] = data;
249  }
250}
251
252@Entry
253@Component
254struct Index {
255  @State currentIndex: number = 0;
256  cacheCount: number = 1
257  swiperController: SwiperController = new SwiperController();
258  private data: MyDataSource = new MyDataSource([]);
259  context = getContext(this);
260
261  aboutToAppear() {
262    let list: MyObject[] = []
263    for (let i = 0; i < 6; i++) {
264      list.push({ description: "", image: this.data.getData(this.currentIndex)?.image })
265    }
266    this.data = new MyDataSource(list)
267  }
268
269  build() {
270    Swiper(this.swiperController) {
271      LazyForEach(this.data, (item: MyObject, index?: number) => {
272        PhotoItem({
273          myIndex: index,
274          dataSource: this.data
275        })
276      })
277    }
278    .cachedCount(this.cacheCount)
279    .curve(Curves.interpolatingSpring(0, 1, 228, 30))
280    .index(this.currentIndex)
281    .indicator(true)
282    .loop(false)
283    // Pre-load resources in the OnAnimationStart callback.
284    .onAnimationStart((index: number, targetIndex: number) => {
285      console.info("onAnimationStart " + index + " " + targetIndex);
286      if (targetIndex !== index) {
287        try {
288          // Obtain a resource manager.
289          const resourceMgr = this.context.resourceManager;
290          // Obtain the ArrayBuffer instance of item.jpg in the rawfile folder.
291          let str = "item" + (targetIndex + this.cacheCount + 2) + ".jpg";
292          resourceMgr.getRawFileContent(str).then((value) => {
293            // Create an ImageSource instance.
294            const imageSource = image.createImageSource(value.buffer);
295            imageSource.createPixelMap().then((value) => {
296              this.data.addData(targetIndex + this.cacheCount + 1, {
297                description: "" + (targetIndex + this.cacheCount + 1),
298                image: value
299              })
300            })
301          })
302        } catch (err) {
303          console.log("error code" + err);
304        }
305      }
306    })
307    .width('100%')
308    .height('100%')
309  }
310}
311
312```
313
314## Takeaway
315
316- A tie-in of the **Swiper ** component's pre-loading mechanism and **LazyForEach** delivers optimal performance improvements.
317
318- The value of **cachedCount** for pre-loading must be set appropriately based on the loading duration of a single child component. A larger value does not necessarily lead to better performance. If a child component takes *N* ms to load, you are advised to set **cachedCount** to a value less than 400/*N*.
319
320- To further improve pre-loading efficiency to meet high-demanding performance requirements, use the **Swiper ** component's pre-loading mechanism together with the **OnAnimationStart** callback.
321