(*******重点******)鸿蒙HarmonyOS应用开发旅程ArkUI实战绘制(九)渲染控制(LazyForEach)
在前面的文章中,我们介绍了HarmonyOS应用开发中的ArkUI框架,并实现了基本的布局和组件。在本文中,我们将继续介绍ArkUI框架中的渲染控制组件——LazyForEach。LazyForEach是ArkUI框架中用于优化长列表渲染的组件,它通过只渲染可见区域内的子元素,来节省资源和提升性能。LazyForEach使用延迟渲染的方式,只有在子元素进入可见区域时才会进行渲染,离开可见区域后又会
在前面的文章中,我们介绍了HarmonyOS应用开发中的ArkUI框架,并实现了基本的布局和组件。在本文中,我们将继续介绍ArkUI框架中的渲染控制组件——LazyForEach。
LazyForEach是ArkUI框架中用于优化长列表渲染的组件,它通过只渲染可见区域内的子元素,来节省资源和提升性能。LazyForEach使用延迟渲染的方式,只有在子元素进入可见区域时才会进行渲染,离开可见区域后又会被销毁。
LazyForEach:数据懒加载
API参数说明见:华为鸿蒙开发者文档LazyForEach API参数说明
LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。
使用限制
LazyForEach必须在容器组件内使用,仅有List、Grid、Swiper以及WaterFlow组件支持数据懒加载(可配置cachedCount属性,即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。
容器组件内使用LazyForEach的时候,只能包含一个LazyForEach。以List为例,同时包含ListItem、ForEach、LazyForEach的情形是不推荐的;同时包含多个LazyForEach也是不推荐的。
LazyForEach在每次迭代中,必须创建且只允许创建一个子组件;即LazyForEach的子组件生成函数有且只有一个根组件。
生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。
允许LazyForEach包含在if/else条件渲染语句中,也允许LazyForEach中出现if/else条件渲染语句。
键值生成器必须针对每个数据生成唯一的值,如果键值相同,将导致键值相同的UI组件渲染出现问题。
LazyForEach必须使用DataChangeListener对象进行更新,对第一个参数dataSource重新赋值会异常;dataSource使用状态变量时,状态变量改变不会触发LazyForEach的UI刷新。
为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,需要生成不同于原来的键值来触发组件刷新。
LazyForEach必须和@Reusable装饰器一起使用才能触发节点复用。使用方法:将@Reusable装饰在LazyForEach列表的组件上,见使用规则。
键值生成规则
在LazyForEach循环渲染过程中,系统会为每个item生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。
LazyForEach提供了一个名为keyGenerator的参数,这是一个函数,开发者可以通过它自定义键值的生成规则。如果开发者没有定义keyGenerator函数,则ArkUI框架会使用默认的键值生成函数,即(item: Object, index: number) => { return viewId + '-' + index.toString(); }, viewId在编译器转换过程中生成,同一个LazyForEach组件内其viewId是一致的。
组件创建规则
在确定键值生成规则后,LazyForEach的第二个参数itemGenerator函数会根据组件创建规则为数据源的每个数组项创建组件。组件的创建包括两种情况:LazyForEach首次渲染和LazyForEach非首次渲染。
首次渲染
生成不同键值
在LazyForEach首次渲染时,会根据上述键值生成规则为数据源的每个数组项生成唯一键值,并创建相应的组件。
/** BasicDataSource代码见文档末尾附件: string类型数组的BasicDataSource代码 **/
class MyDataSource extends BasicDataSource {
private dataArray: string[] = [];
public totalCount(): number {
return this.dataArray.length;
}
public getData(index: number): string {
return this.dataArray[index];
}
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0, data);
this.notifyDataAdd(index);
}
public pushData(data: string): void {
this.dataArray.push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Text(item).fontSize(50)
.onAppear(() => {
console.info("appear:" + item)
})
}.margin({ left: 10, right: 10 })
}
}, (item: string) => item)
}.cachedCount(5)
}
}
键值相同时错误渲染
当不同数据项生成的键值相同时,框架的行为是不可预测的。例如,在以下代码中,LazyForEach渲染的数据项键值均相同,在滑动过程中,LazyForEach会对划入划出当前页面的子组件进行预加载,而新建的子组件和销毁的原子组件具有相同的键值,框架可能存在取用缓存错误的情况,导致子组件渲染有问题。
/** BasicDataSource代码见文档末尾附件: string类型数组的BasicDataSource代码 **/
class MyDataSource extends BasicDataSource {
private dataArray: string[] = [];
public totalCount(): number {
return this.dataArray.length;
}
public getData(index: number): string {
return this.dataArray[index];
}
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0, data);
this.notifyDataAdd(index);
}
public pushData(data: string): void {
this.dataArray.push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Text(item).fontSize(50)
.onAppear(() => {
console.info("appear:" + item)
})
}.margin({ left: 10, right: 10 })
}
}, (item: string) => 'same key')
}.cachedCount(5)
}
}
非首次渲染
当LazyForEach数据源发生变化,需要再次渲染时,开发者应根据数据源的变化情况调用listener对应的接口,通知LazyForEach做相应的更新,各使用场景如下。
/** BasicDataSource代码见文档末尾附件: string类型数组的BasicDataSource代码 **/
class MyDataSource extends BasicDataSource {
private dataArray: string[] = [];
public totalCount(): number {
return this.dataArray.length;
}
public getData(index: number): string {
return this.dataArray[index];
}
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0, data);
this.notifyDataAdd(index);
}
public pushData(data: string): void {
this.dataArray.push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Text(item).fontSize(50)
.onAppear(() => {
console.info("appear:" + item)
})
}.margin({ left: 10, right: 10 })
}
.onClick(() => {
// 点击追加子组件
this.data.pushData(`Hello ${this.data.totalCount()}`);
})
}, (item: string) => item)
}.cachedCount(5)
}
}
当我们点击LazyForEach的子组件时,首先调用数据源data的pushData方法,该方法会在数据源末尾添加数据并调用notifyDataAdd方法。在notifyDataAdd方法内会又调用listener.onDataAdd方法,该方法会通知LazyForEach在该处有数据添加,LazyForEach便会在该索引处新建子组件。
删除数据
/** BasicDataSource代码见文档末尾附件: string类型数组的BasicDataSource代码 **/
class MyDataSource extends BasicDataSource {
private dataArray: string[] = [];
public totalCount(): number {
return this.dataArray.length;
}
public getData(index: number): string {
return this.dataArray[index];
}
public getAllData(): string[] {
return this.dataArray;
}
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0, data);
this.notifyDataAdd(index);
}
public pushData(data: string): void {
this.dataArray.push(data);
}
public deleteData(index: number): void {
this.dataArray.splice(index, 1);
this.notifyDataDelete(index);
}
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string, index: number) => {
ListItem() {
Row() {
Text(item).fontSize(50)
.onAppear(() => {
console.info("appear:" + item)
})
}.margin({ left: 10, right: 10 })
}
.onClick(() => {
// 点击删除子组件
this.data.deleteData(this.data.getAllData().indexOf(item));
})
}, (item: string) => item)
}.cachedCount(5)
}
}
当我们点击LazyForEach的子组件时,首先调用数据源data的deleteData方法,该方法会删除数据源对应索引处的数据并调用notifyDataDelete方法。在notifyDataDelete方法内会又调用listener.onDataDelete方法,该方法会通知LazyForEach在该处有数据删除,LazyForEach便会在该索引处删除对应子组件。
LazyForEach:数据懒加载:要用到的地方很多
应用的场景在数据量非常大的时候考虑的性能问题
这个大家要好好学习理解详细请参考华为鸿蒙开发者文档
下节文章我们对UI开发进行讲解
更多推荐
所有评论(0)