【HarmonyOS】List组件多层对象嵌套ForEach渲染更新的处理
问题背景: 在鸿蒙中UI更新渲染的机制,与传统的Android IOS应用开发相比。开发会简单许多,开发效率提升显著。
一般传统应用开发的流程处理分为三步:1.画UI,2.获得or创建,处理数据,3.增删改数据,找到对应控件,更新到UI上。
而鸿蒙应用开发,大大提供效率其中一点,就是减少了第三步。我们只需要关心数据源的变化,数据自动会更新到对应的控件上。
这种处理机制,其实符合应用开发的时代潮流,目前Android和IOS最新框架机制都有相应类似的处理。例如swiftUI,Compose。并且Vue,Flutter整个刷新机制就是如此。
众所周知,在HarmonyOs的list组件渲染中,如果数据源列表对象是多个对象嵌套的处理,那最底层对象的属性更新时,UI界面是不会渲染的。因为检测不到,目前只能检测到第一层对象。
解决方案:
当然官方的api在持续迭代的过程中。对于冗余渲染,渲染的性能提升,还有多层对象嵌套的数据源更新问题,一直再迭代方法处理。
针对多层对象嵌套,底层对象属性修改后,UI不渲染的问题,有个比较简单又方便的处理方式,思路仅供参考。
即:深拷贝修改的item对象。
这样整个对象相当于都变化了,就符合第一层对象检测的机制,可以被系统捕获到刷新。
DEMO示例:
import { util } from '@kit.ArkTS';
/**
* 二级数据结构
*/
class ChildInfo {
index: number;
constructor(index: number) {
this.index = index;
}
}
/**
* 一级数据结构
*/
class ItemInfo {
key: string = util.generateRandomUUID(true);
name: string;
icon: Resource;
childInfo: ChildInfo;
select: boolean;
constructor(name: string, icon: Resource, index: number) {
this.name = name;
this.icon = icon;
this.childInfo = new ChildInfo(index);
this.select = false;
}
/**
* 重新创建对象,深拷贝处理
* @param itemInfo
* @param index
* @returns
*/
static deepCopy(itemInfo: ItemInfo, index: number){
let info: ItemInfo = new ItemInfo(itemInfo.name, itemInfo.icon, index);
info.select = itemInfo.select;
info.key = itemInfo.key;
info.childInfo = itemInfo.childInfo;
return info;
}
}
/**
*
*/
@Entry
@Component
struct ForeachPage {
private TAG: string = "ForeachPage";
@State mListData: Array<ItemInfo> = [];
aboutToAppear(): void {
this.mListData.push(new ItemInfo('游戏', $r("app.media.iconA"), 1));
this.mListData.push(new ItemInfo('游戏', $r("app.media.iconB"), 2));
this.mListData.push(new ItemInfo('游戏', $r("app.media.iconA"), 3));
this.mListData.push(new ItemInfo('游戏', $r("app.media.iconB"), 4));
this.mListData.push(new ItemInfo('游戏', $r("app.media.iconA"), 5));
this.mListData.push(new ItemInfo('游戏', $r("app.media.iconB"), 6));
}
@Builder ItemView(item: ItemInfo, index: number){
Row() {
Image(item.icon)
.width(px2vp(120))
.height(px2vp(120))
Text(item.name + "(" + item.childInfo.index + ")").fontSize(20)
Blank()
if(this.isLog(item, index)){
if(item.select){
Image($r("app.media.icon_check"))
.size({
width: px2vp(72),
height: px2vp(72)
})
}
}
}
.width('100%')
.justifyContent(FlexAlign.Start)
.onClick(()=>{
item.select = !item.select;
if(item.select){
item.childInfo.index = 666;
}else{
item.childInfo.index = index;
}
this.mListData[index] = ItemInfo.deepCopy(item, item.childInfo.index);
console.log(this.TAG, " ItemView onClick: " + index + " item.select: " + item.select);
})
}
private isLog(item: ItemInfo, index: number){
console.log(this.TAG, " ItemView isLog index: " + index + " item.select: " + item.select);
return true;
}
build() {
List() {
ForEach(this.mListData, (item: ItemInfo, index: number) => {
ListItem() {
this.ItemView(item, index)
}
}, (item: ItemInfo) => JSON.stringify(item))
//
}
.width("100%")
.height("100%")
.padding({ left: px2vp(60), right: px2vp(60) })
}
}