背景
在應(yīng)用開發(fā)中,Swiper 組件常用于翻頁(yè)場(chǎng)景乌企,比如:桌面阁最、圖庫(kù)等應(yīng)用戒祠。Swiper 組件滑動(dòng)切換頁(yè)面時(shí),基于按需加載原則通常會(huì)在下一個(gè)頁(yè)面將要顯示時(shí)才對(duì)該頁(yè)面進(jìn)行加載和布局繪制速种,這個(gè)過(guò)程包括:
如果該頁(yè)面使用了@Component 裝飾的自定義組件姜盈,那么自定義組件的 build 函數(shù)會(huì)被執(zhí)行并創(chuàng)建內(nèi)部的 UI 組件;
如果使用了LazyForEach配阵,會(huì)執(zhí)行 LazyForEach 的 UI 生成函數(shù)生成 UI 組件馏颂;
在 UI 組件構(gòu)建完成后,會(huì)對(duì) UI 組件進(jìn)行布局測(cè)算和繪制棋傍。
針對(duì)復(fù)雜頁(yè)面場(chǎng)景救拉,該過(guò)程可能會(huì)持續(xù)較長(zhǎng)時(shí)間,導(dǎo)致滑動(dòng)過(guò)程中出現(xiàn)卡頓瘫拣,對(duì)滑動(dòng)體驗(yàn)造成負(fù)面影響亿絮,甚至成為整個(gè)應(yīng)用的性能瓶頸。如在圖庫(kù)大圖瀏覽場(chǎng)景中麸拄,若不使用預(yù)加載機(jī)制派昧,每次都將在滑動(dòng)開始的首幀去加載下一張圖片,會(huì)導(dǎo)致首幀耗時(shí)過(guò)長(zhǎng)甚至掉幀拢切,拖慢應(yīng)用性能斗锭。
為了解決上述問(wèn)題,可以使用 Swiper 組件的預(yù)加載機(jī)制失球,利用主線程的空閑時(shí)間來(lái)提前構(gòu)建和布局繪制組件,優(yōu)化滑動(dòng)體驗(yàn)实苞。
使用場(chǎng)景
如果開發(fā)者的應(yīng)用場(chǎng)景屬于加載較為耗時(shí)的場(chǎng)景時(shí)烈疚,尤其是下列場(chǎng)景,推薦使用 Swiper 預(yù)加載功能爷肝。
Swiper 的子組件大于等于五個(gè)猾浦;
Swiper 的子組件具有復(fù)雜的動(dòng)畫;
Swiper 的子組件加載時(shí)需要執(zhí)行網(wǎng)絡(luò)請(qǐng)求等耗時(shí)操作金赦;
Swiper 的子組件包含大量需要渲染的圖像或資源对嚼。
Swiper 預(yù)加載機(jī)制說(shuō)明
預(yù)加載機(jī)制是 Swiper 組件中一個(gè)重要的特性夹抗,允許 Swiper 滑動(dòng)到下一個(gè)子組件之前提前加載后續(xù)頁(yè)面的內(nèi)容,其主要目的是提高應(yīng)用滑動(dòng)時(shí)的流暢性和響應(yīng)速度纵竖。當(dāng)用戶嘗試滑動(dòng)到下一個(gè)子組件時(shí)漠烧,如果下一個(gè)子組件的內(nèi)容已經(jīng)提前加載完畢已脓,那么滑動(dòng)就會(huì)立即發(fā)生,否則 Swiper 組件需要在加載下一個(gè)子組件的同時(shí)處理滑動(dòng)事件度液,對(duì)滑動(dòng)體驗(yàn)造成負(fù)面影響恨诱。當(dāng)前 Swiper 組件的預(yù)加載在用戶滑動(dòng)離手動(dòng)效開始時(shí)觸發(fā)媳瞪,離手動(dòng)效的計(jì)算在渲染線程中進(jìn)行蛇受,因此主線程有空閑的時(shí)間可以進(jìn)行預(yù)加載的操作厕鹃。配合 LazyForEach 的按需加載和銷毀能力,可以在優(yōu)化滑動(dòng)體驗(yàn)基礎(chǔ)上節(jié)省內(nèi)存占用把将。
使用指導(dǎo)
- 預(yù)加載子組件的個(gè)數(shù)在cachedCount屬性中配置忆矛。
Swiper 共 5 頁(yè),當(dāng)開發(fā)者設(shè)置了 cacheCount 屬性為 1 且 loop 屬性為 false 時(shí)洽议,預(yù)加載的結(jié)果如下:\
Swiper 共 5 頁(yè)漫拭,當(dāng)開發(fā)者設(shè)置了 cacheCount 屬性為 1 且 loop 屬性為 true 時(shí),預(yù)加載的結(jié)果如下:\
- Swiper 組件的子組件使用LazyForEach動(dòng)態(tài)加載和銷毀組件审胚。
示例
class MyDataSource implements IDataSource { // LazyForEach的數(shù)據(jù)源
private list: number[] = [];
constructor(list: number[]) {
this.list = list;
}
totalCount(): number {
return this.list.length;
}
getData(index: number): number {
return this.list[index];
}
registerDataChangeListener(_: DataChangeListener): void {
}
unregisterDataChangeListener(): void {
}
}
@Component
struct SwiperChildPage { // Swiper的子組件
@State arr: number[] = [];
aboutToAppear(): void {
for (let i = 1; i <= 100; i++) {
this.arr.push(i);
}
}
build() {
Column() {
List({ space: 20 }) {
ForEach(this.arr, (index: number) => {
ListItem() {
Text(index.toString())
.height('4.5%')
.fontSize(16)
.textAlign(TextAlign.Center)
.backgroundColor(0xFFFFFF)
}
.border({ width: 2, color: Color.Green })
}, (index: number) => index.toString());
}
.height("95%")
.width("95%")
.border({ width: 3, color: Color.Red })
.lanes({ minLength: 40, maxLength: 40 })
.alignListItem(ListItemAlign.Start)
.scrollBar(BarState.Off)
}.width('100%').height('100%').padding({ top: 5 });
}
}
@Entry
@Preview
@Component
struct SwiperExample {
private dataSrc: MyDataSource = new MyDataSource([]);
aboutToAppear(): void {
let list: Array<number> = []
for (let i = 1; i <= 10; i++) {
list.push(i);
}
this.dataSrc = new MyDataSource(list);
}
build() {
Column({ space: 5 }) {
Swiper() {
LazyForEach(this.dataSrc, (_: number) => {
SwiperChildPage();
}, (item: number) => item.toString());
}
.loop(false)
.cachedCount(1) // 提前加載后一項(xiàng)的內(nèi)容
.indicator(true)
.duration(100)
.displayArrow({
showBackground: true,
isSidebarMiddle: true,
backgroundSize: 40,
backgroundColor: Color.Orange,
arrowSize: 25,
arrowColor: Color.Black
}, false)
.curve(Curve.Linear)
}.width('100%')
.margin({ top: 5 })
}
}
驗(yàn)證效果
為了更好地體現(xiàn) Swiper 預(yù)加載機(jī)制帶來(lái)的性能優(yōu)化效果,用例采用下列前置條件:
Swiper 的子組件為帶有 100 個(gè) ListItem 的 List 組件痘系;
Swiper 組件共有 10 個(gè) List 子組件。
在該場(chǎng)景下临谱,使用 Swiper 預(yù)加載機(jī)制可以為每個(gè)翻頁(yè)動(dòng)作節(jié)省約40%的時(shí)間,同時(shí)保證翻頁(yè)時(shí)不丟幀城豁,保證翻頁(yè)的流暢度抄课。
優(yōu)化建議
由于組件構(gòu)建和布局計(jì)算需要一定時(shí)間,cachedCount 的數(shù)量也不是設(shè)置得越大越好间聊,過(guò)大的 cachedCount 可能會(huì)導(dǎo)致應(yīng)用性能降低抵拘。當(dāng)前 Swiper 組件滑動(dòng)離手后的動(dòng)效時(shí)間大約是 400ms,如果應(yīng)用加載一個(gè)子組件的時(shí)間在 100ms~200ms 之間尚蝌,為了在離手動(dòng)效時(shí)間內(nèi)完成組件的預(yù)加載充尉,cachedCount 屬性建議設(shè)置為 1 或 2,設(shè)置過(guò)大會(huì)導(dǎo)致主線程阻塞而產(chǎn)生卡頓姿鸿。
那么方案可以繼續(xù)優(yōu)化倒源,Swiper 組件有一個(gè)OnAnimationStart回調(diào)接口相速,切換動(dòng)畫開始時(shí)觸發(fā)該回調(diào)鲜锚。此時(shí),主線程空閑旺隙,應(yīng)用可以充分利用這段時(shí)間進(jìn)行圖片等資源的預(yù)加載骏令,減少后續(xù) cachedCount 范圍內(nèi)的節(jié)點(diǎn)預(yù)加載耗時(shí)。
示例
Swiper 子組件頁(yè)面代碼如下:
在子組件首次構(gòu)建(生命周期執(zhí)行到aboutToAppear)時(shí)周拐,先判斷 dataSource 中該 index 的數(shù)據(jù)是否有數(shù)據(jù),若無(wú)數(shù)據(jù)則先進(jìn)行資源加載审丘,再構(gòu)建節(jié)點(diǎn)勾给。若有數(shù)據(jù),則直接構(gòu)建節(jié)點(diǎn)即可脓钾。
import image from '@ohos.multimedia.image';
import { MyDataSource } from './Index'
@Component
export struct PhotoItem { //Swiper的子組件
myIndex: number = 0;
private dataSource: MyDataSource = new MyDataSource([]);
context = getContext(this);
@State imageContent: image.PixelMap | undefined = undefined;
aboutToAppear(): void {
console.info(`aboutToAppear` + this.myIndex);
this.imageContent = this.dataSource.getData(this.myIndex)?.image;
if (!this.imageContent) { // 先判斷dataSource中該index的數(shù)據(jù)是否有數(shù)據(jù)桩警,若無(wú)數(shù)據(jù)則先進(jìn)行資源加載
try {
// 獲取resourceManager資源管理器
const resourceMgr = this.context.resourceManager;
// 獲取rawfile文件夾下item.jpg的ArrayBuffer
let str = "item" + (this.myIndex + 1) + ".jpg";
resourceMgr.getRawFileContent(str).then((value) => {
// 創(chuàng)建imageSource
const imageSource = image.createImageSource(value.buffer);
imageSource.createPixelMap().then((value) => {
console.log("aboutToAppear push" + this.myIndex)
this.dataSource.addData(this.myIndex, { description: "" + this.myIndex, image: value })
this.imageContent = value;
})
})
} catch (err) {
console.log("error code" + err);
}
}
}
build() {
Column() {
Image(this.imageContent)
.width("100%")
.height("100%")
}
}
}
Swiper 主頁(yè)面的代碼如下:
import Curves from '@ohos.curves';
import { PhotoItem } from './PhotoItem'
import image from '@ohos.multimedia.image';
interface MyObject {
description: string,
image: image.PixelMap,
};
export class MyDataSource implements IDataSource {
private list: MyObject[] = []
constructor(list: MyObject[]) {
this.list = list
}
totalCount(): number {
return this.list.length
}
getData(index: number): MyObject {
return this.list[index]
}
registerDataChangeListener(listener: DataChangeListener): void {
}
unregisterDataChangeListener(listener: DataChangeListener): void {
}
addData(index: number, data: MyObject) {
this.list[index] = data;
}
}
@Entry
@Component
struct Index {
@State currentIndex: number = 0;
cacheCount: number = 1
swiperController: SwiperController = new SwiperController();
private data: MyDataSource = new MyDataSource([]);
context = getContext(this);
aboutToAppear() {
let list: MyObject[] = []
for (let i = 0; i < 6; i++) {
list.push({ description: "", image: this.data.getData(this.currentIndex)?.image })
}
this.data = new MyDataSource(list)
}
build() {
Swiper(this.swiperController) {
LazyForEach(this.data, (item: MyObject, index?: number) => {
PhotoItem({
myIndex: index,
dataSource: this.data
})
})
}
.cachedCount(this.cacheCount)
.curve(Curves.interpolatingSpring(0, 1, 228, 30))
.index(this.currentIndex)
.indicator(true)
.loop(false)
// 在OnAnimationStart接口回調(diào)中進(jìn)行預(yù)加載資源的操作
.onAnimationStart((index: number, targetIndex: number) => {
console.info("onAnimationStart " + index + " " + targetIndex);
if (targetIndex !== index) {
try {
// 獲取resourceManager資源管理器
const resourceMgr = this.context.resourceManager;
// 獲取rawfile文件夾下item.jpg的ArrayBuffer
let str = "item" + (targetIndex + this.cacheCount + 2) + ".jpg";
resourceMgr.getRawFileContent(str).then((value) => {
// 創(chuàng)建imageSource
const imageSource = image.createImageSource(value.buffer);
imageSource.createPixelMap().then((value) => {
this.data.addData(targetIndex + this.cacheCount + 1, {
description: "" + (targetIndex + this.cacheCount + 1),
image: value
})
})
})
} catch (err) {
console.log("error code" + err);
}
}
})
.width('100%')
.height('100%')
}
}
總結(jié)
Swiper 組件的預(yù)加載機(jī)制與 LazyForEach 結(jié)合使用,能夠達(dá)到最佳優(yōu)化效果柱蟀。
預(yù)加載的 cachedCount 并非越大越好,需要結(jié)合單個(gè)子組件加載耗時(shí)來(lái)設(shè)置畜眨。假設(shè)一個(gè)子組件的加載耗時(shí)為 Nms术瓮,那么 cachedCount 推薦設(shè)置為小于 400/N。
如果應(yīng)用有非常高的性能優(yōu)化需求恬汁,Swiper 預(yù)加載機(jī)制可搭配 OnAnimationStart 接口回調(diào)使用辜伟,進(jìn)一步提升預(yù)加載的效率。
寫在最后
如果你覺得這篇內(nèi)容對(duì)你還蠻有幫助约巷,我想邀請(qǐng)你幫我三個(gè)小忙:
- 點(diǎn)贊旱捧,轉(zhuǎn)發(fā)踩麦,有你們的 『點(diǎn)贊和評(píng)論』谓谦,才是我創(chuàng)造的動(dòng)力顽铸。
- 關(guān)注小編,同時(shí)可以期待后續(xù)文章ing??星压,不定期分享原創(chuàng)知識(shí)鬼譬。
- 想要獲取更多完整鴻蒙最新學(xué)習(xí)知識(shí)點(diǎn),請(qǐng)移步前往小編:
https://gitee.com/MNxiaona/733GH/blob/master/jianshu