LazyForEach 长列表滑动与渲染底层优化
列表滑动帧率(FPS)是检验应用体验最直观的标尺。当一个列表的数据量达到几千甚至上万条时,如果你还在用 ForEach 去渲染,那每一帧的渲染压力足以让旗舰机掉帧。要解决这个问题,必须深入理解 ArkUI 的渲染管线与虚拟列表机制。
虚拟列表的基础 IDataSource
LazyForEach 并不是简单地把数组传进去就行,它要求你提供一个数据源对象,实现 IDataSource 接口。它的核心逻辑是:系统只渲染屏幕上看得见的节点,滑出屏幕外的节点会被回收进缓存池。
typescript
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = [];
private dataArray: ItemData[] = [];
totalCount(): number {
return this.dataArray.length;
}
getData(index: number): ItemData {
return this.dataArray[index];
}
// 省略 register 和 unregister 方法
// 注意:必须通过监听器通知 UI 更新
public pushData(item: ItemData): void {
this.dataArray.push(item);
this.listeners.forEach(listener => listener.onDataAdd(this.dataArray.length - 1));
}
}
组件复用:@Reusable 是性能的灵魂
仅仅用 LazyForEach,节点只是被销毁和重建。如果列表项里的 UI 结构极其复杂(包含多层图片、富文本),每次滑动重建 build() 依然会引发卡顿。
必须搭配 @Reusable 装饰器,让组件能够被真正“复用”。
typescript
@Reusable
@Component
struct ListItemCard {
@State item: ItemData = {} as ItemData;
// 关键警示:被复用时,aboutToAppear 不会触发!
// 必须在这个回调里更新数据
aboutToReuse(params: Record<string, Object>): void {
this.item = params.data as ItemData;
}
build() { /*...*/ }
}
当组件被复用时,系统会直接拿缓存里的组件,把新数据塞进去。这省去了组件实例化和布局计算的时间,是长列表丝滑的关键所在。
if/else 带来的组件重建灾难
在列表里,我们经常用 if/else 去控制不同状态的显示。比如按钮的选中状态:
typescript
if (this.item.isSelected) {
Button('已选中').backgroundColor(Color.Green)
} else {
Button('未选中').backgroundColor(Color.Gray)
}
每次用户点击,if 条件切换,底层的 ArkUI 就会把前一个按钮销毁,重新创建另一个。对于几千条数据的列表,这会引发巨大的性能开销。
优化手段: 只要 UI 的骨架结构一致,仅仅是属性变化,一定要用 visibility 属性,或者条件判断放在属性值里,绝不要在结构层用 if/else。
typescript
Button(this.item.isSelected ? '已选中' : '未选中') .backgroundColor(this.item.isSelected ? Color.Green : Color.Gray)
布局深度的降维打击
ArkUI 在进行布局排版(Measure/Layout)时,是自上而下递归计算的。如果你用 Column 嵌套 Row,再嵌套 Column,层级达到了七八层,每次滑动系统都要重新计算所有子节点的尺寸,CPU 直接拉满。
尽量用 RelativeContainer 把层级扁平化。或者使用 Flex 布局,注意控制嵌套深度在 3 层以内。这对长列表的性能提升是决定性的。
写一个包含一万条数据的列表,加上 @Reusable。在滑动时打开性能分析工具,试着在 build() 里加一个复杂的数学计算,观察帧率瞬间掉到 30 以下的惨状,体会一下 UI 线程的脆弱。
更多推荐
所有评论(0)