HarmonyOS 鸿蒙Next中在应用展示大量数据时如何避免卡顿?
HarmonyOS 鸿蒙Next中在应用展示大量数据时如何避免卡顿? 在鸿蒙应用中展示大量数据时,如何避免卡顿? ForEach和LazyForEach有什么区别? 如何实现高性能的列表滚动?
4 回复
官方文档解释得挺清楚的呀,就是全量加载和按需加载的区别
更多关于HarmonyOS 鸿蒙Next中在应用展示大量数据时如何避免卡顿?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
实现代码
/**
* 数据源实现 - LazyForEach必需
*/
class RecordDataSource implements IDataSource {
private records: HumanRecord[] = [];
private listeners: DataChangeListener[] = [];
constructor(records: HumanRecord[]) {
this.records = records;
}
// 获取数据总数
totalCount(): number {
return this.records.length;
}
// 获取指定位置的数据
getData(index: number): HumanRecord {
return this.records[index];
}
// 注册数据改变监听器
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
this.listeners.push(listener);
}
}
// 注销数据改变监听器
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
this.listeners.splice(pos, 1);
}
}
// 通知数据重新加载
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded();
});
}
// 通知数据添加
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index);
});
}
// 通知数据改变
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index);
});
}
// 通知数据删除
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index);
});
}
// 添加数据
public addData(record: HumanRecord): void {
this.records.push(record);
this.notifyDataAdd(this.records.length - 1);
}
// 更新数据
public updateData(index: number, record: HumanRecord): void {
this.records[index] = record;
this.notifyDataChange(index);
}
// 删除数据
public deleteData(index: number): void {
this.records.splice(index, 1);
this.notifyDataDelete(index);
}
// 重新加载数据
public reloadData(records: HumanRecord[]): void {
this.records = records;
this.notifyDataReload();
}
}
/**
* 记录列表页面 - 使用LazyForEach优化
*/
@Entry
@Component
struct RecordListPage {
@State recordDataSource: RecordDataSource = new RecordDataSource([]);
@State loading: boolean = true;
@State primaryColor: string = '#FA8C16';
private dataService: DataService = DataService.getInstance();
private scroller: Scroller = new Scroller();
aboutToAppear() {
this.loadData();
this.loadThemeColor();
}
/**
* 加载数据
*/
private async loadData() {
try {
this.loading = true;
const records = await this.dataService.getAllRecords(
undefined,
{ field: 'eventTime', order: 'desc' }
);
// 更新数据源
this.recordDataSource.reloadData(records);
this.loading = false;
} catch (error) {
console.error('加载数据失败:', JSON.stringify(error));
this.loading = false;
}
}
/**
* 加载主题颜色
*/
private loadThemeColor() {
this.primaryColor = ThemeConstants.getPrimaryColor();
}
build() {
Column() {
// 导航栏
this.buildHeader()
if (this.loading) {
// 加载中
this.buildLoadingView()
} else {
// 列表内容
this.buildListView()
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
/**
* 构建导航栏
*/
@Builder
buildHeader() {
Row() {
Image($r('app.media.ic_back'))
.width(24)
.height(24)
.fillColor('#FFFFFF')
.onClick(() => router.back())
Text('人情记录')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.margin({ left: 16 })
}
.width('100%')
.height(60)
.padding({ left: 20, right: 20 })
.backgroundColor(this.primaryColor)
}
/**
* 构建列表视图 - 使用LazyForEach
*/
@Builder
buildListView() {
List({ scroller: this.scroller }) {
// 使用LazyForEach实现懒加载
LazyForEach(
this.recordDataSource,
(record: HumanRecord, index: number) => {
ListItem() {
this.buildRecordItem(record, index)
}
.swipeAction({ end: this.buildSwipeAction(record, index) })
},
(record: HumanRecord) => record.id
)
}
.width('100%')
.layoutWeight(1)
.edgeEffect(EdgeEffect.Spring)
.divider({
strokeWidth: 1,
color: '#F0F0F0',
startMargin: 16,
endMargin: 16
})
.cachedCount(3) // 缓存3个item,提升滚动性能
.friction(0.6) // 设置摩擦系数
}
/**
* 构建记录项
*/
@Builder
buildRecordItem(record: HumanRecord, index: number) {
Row() {
// 左侧图标
Column() {
Text(this.getEventTypeIcon(record.eventType))
.fontSize(24)
}
.width(50)
.height(50)
.backgroundColor(this.getEventTypeColor(record.eventType) + '20')
.borderRadius(25)
.justifyContent(FlexAlign.Center)
.margin({ right: 12 })
// 中间信息
Column() {
Row() {
Text(record.personName || '未知')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#262626')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(this.getEventTypeName(record.eventType))
.fontSize(12)
.fontColor('#8C8C8C')
.margin({ left: 8 })
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.backgroundColor('#F5F5F5')
.borderRadius(4)
}
.width('100%')
.margin({ bottom: 4 })
Text(this.formatDate(record.eventTime))
.fontSize(12)
.fontColor('#8C8C8C')
if (record.remark) {
Text(record.remark)
.fontSize(12)
.fontColor('#595959')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ top: 4 })
}
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
// 右侧金额
Column() {
Text(record.type === 'received' ? '+' : '-')
.fontSize(14)
.fontColor(record.type === 'received' ? '#52C41A' : '#FF4D4F')
Text(`¥${record.amount}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(record.type === 'received' ? '#52C41A' : '#FF4D4F')
}
.alignItems(HorizontalAlign.End)
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.onClick(() => {
this.onRecordClick(record);
})
}
/**
* 构建滑动操作
*/
@Builder
buildSwipeAction(record: HumanRecord, index: number) {
Row() {
// 编辑按钮
Button('编辑')
.fontSize(14)
.fontColor('#FFFFFF')
.backgroundColor('#1890FF')
.width(60)
.height('100%')
.onClick(() => {
this.onEditRecord(record);
})
// 删除按钮
Button('删除')
.fontSize(14)
.fontColor('#FFFFFF')
.backgroundColor('#FF4D4F')
.width(60)
.height('100%')
.onClick(() => {
this.onDeleteRecord(record, index);
})
}
}
/**
* 构建加载视图
*/
@Builder
buildLoadingView() {
Column() {
LoadingProgress()
.width(40)
.height(40)
.color(this.primaryColor)
Text('加载中...')
.fontSize(14)
.fontColor('#8C8C8C')
.margin({ top: 16 })
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
/**
* 记录点击事件
*/
private onRecordClick(record: HumanRecord) {
router.pushUrl({
url: 'pages/RecordDetailPage',
params: { recordId: record.id }
});
}
/**
* 编辑记录
*/
private onEditRecord(record: HumanRecord) {
router.pushUrl({
url: 'pages/EditRecordPage',
params: { recordId: record.id }
});
}
/**
* 删除记录
*/
private async onDeleteRecord(record: HumanRecord, index: number) {
try {
await this.dataService.deleteRecord(record.id);
this.recordDataSource.deleteData(index);
promptAction.showToast({
message: '删除成功',
duration: 2000
});
} catch (error) {
promptAction.showToast({
message: '删除失败',
duration: 2000
});
}
}
/**
* 格式化日期
*/
private formatDate(timestamp: number): string {
const date = new Date(timestamp);
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
}
/**
* 获取事件类型图标
*/
private getEventTypeIcon(eventType: string): string {
const iconMap: Record<string, string> = {
'wedding': '🎉',
'funeral': '🕊️',
'birthday': '🎂',
'full_moon': '👶',
'graduation': '🎓',
'moving': '🏠',
'holiday': '🎊',
'visit_sick': '🏥',
'other': '📝'
};
return iconMap[eventType] || '📝';
}
/**
* 获取事件类型名称
*/
private getEventTypeName(eventType: string): string {
const nameMap: Record<string, string> = {
'wedding': '婚礼',
'funeral': '丧礼',
'birthday': '生日',
'full_moon': '满月',
'graduation': '升学',
'moving': '乔迁',
'holiday': '节日',
'visit_sick': '探病',
'other': '其他'
};
return nameMap[eventType] || '其他';
}
/**
* 获取事件类型颜色
*/
private getEventTypeColor(eventType: string): string {
const colorMap: Record<string, string> = {
'wedding': '#FF6B6B',
'funeral': '#4ECDC4',
'birthday': '#45B7D1',
'full_moon': '#96CEB4',
'graduation': '#FFEAA7',
'moving': '#DDA0DD',
'holiday': '#FFB347',
'visit_sick': '#87CEEB',
'other': '#D3D3D3'
};
return colorMap[eventType] || '#D3D3D3';
}
}
ForEach vs LazyForEach
ForEach (一次性渲染)
List() {
ForEach(records, (record: HumanRecord) => {
ListItem() {
// 所有item一次性创建
}
})
}
问题:
- ❌ 数据量大时卡顿
- ❌ 内存占用高
- ❌ 首屏加载慢
LazyForEach (按需渲染)
List() {
LazyForEach(dataSource, (record: HumanRecord) => {
ListItem() {
// 只创建可见的item
}
}, (record: HumanRecord) => record.id)
}
优势:
- ✅ 按需加载,性能好
- ✅ 内存占用低
- ✅ 支持大数据量
性能优化关键参数
1. cachedCount
List() {
// ...
}
.cachedCount(3) // 缓存3个item
作用: 缓存屏幕外的item数量 建议值: 3-5
2. friction
List() {
// ...
}
.friction(0.6) // 摩擦系数
作用: 控制滑动阻力 建议值: 0.6-0.9
3. edgeEffect
List() {
// ...
}
.edgeEffect(EdgeEffect.Spring) // 弹性效果
可选值:
EdgeEffect.Spring: 弹性效果EdgeEffect.Fade: 渐隐效果EdgeEffect.None: 无效果
数据源实现要点
1. 必须实现IDataSource接口
class DataSource implements IDataSource {
totalCount(): number { }
getData(index: number): any { }
registerDataChangeListener(listener: DataChangeListener): void { }
unregisterDataChangeListener(listener: DataChangeListener): void { }
}
2. 数据更新通知
// 添加数据
this.notifyDataAdd(index);
// 更新数据
this.notifyDataChange(index);
// 删除数据
this.notifyDataDelete(index);
// 重新加载
this.notifyDataReload();
3. 唯一标识符
LazyForEach(
dataSource,
(item) => { },
(item) => item.id // 必须提供唯一key
)
性能对比
ForEach渲染
- 1000条数据: 首屏加载 ~3秒
- 内存占用: ~50MB
- 滚动帧率: 30-40fps
LazyForEach渲染
- 1000条数据: 首屏加载 ~0.5秒
- 内存占用: ~15MB
- 滚动帧率: 55-60fps
性能提升: 约6倍
最佳实践
1. 合理设置cachedCount
// 数据简单: 缓存多一些
.cachedCount(5)
// 数据复杂: 缓存少一些
.cachedCount(2)
2. 避免在item中执行耗时操作
// ❌ 错误: 在item中查询数据库
@Builder
buildItem(record: HumanRecord) {
const person = await this.dataService.getPersonById(record.personId);
}
// ✅ 正确: 预先加载数据
aboutToAppear() {
this.preloadData();
}
3. 使用@Reusable提升复用
[@Reusable](/user/Reusable)
@Component
struct RecordItem {
@State record: HumanRecord | null = null;
aboutToReuse(params: Record<string, Object>) {
this.record = params.record as HumanRecord;
}
}
4. 图片懒加载
Image(record.avatar)
.alt($r('app.media.default_avatar')) // 默认图片
.objectFit(ImageFit.Cover)
避坑指南
1. ❌ 忘记实现keyGenerator
// 错误: 没有提供key
LazyForEach(dataSource, (item) => { })
// 正确: 提供唯一key
LazyForEach(dataSource, (item) => { }, (item) => item.id)
2. ❌ 数据更新后不通知
// 错误: 直接修改数据
this.records[0] = newRecord;
// 正确: 通知数据变化
this.dataSource.updateData(0, newRecord);
3. ❌ 在item中使用复杂计算
// 错误: 每次渲染都计算
Text(this.calculateComplexValue(record))
// 正确: 预先计算好
Text(record.cachedValue)
在HarmonyOS Next中处理大量数据展示并避免卡顿,核心在于优化列表渲染。以下是针对您问题的关键技术点:
-
ForEach与LazyForEach的核心区别:
- ForEach:适用于静态、数据量有限的数组。它会立即创建所有数据项对应的组件,当数据量大时,会占用大量内存并导致初始渲染卡顿。
- LazyForEach:专为动态、海量数据设计。它采用按需加载机制,仅创建和渲染当前可视区域及少量缓冲区的组件。当列表滚动时,它会复用离开屏幕的组件来展示新数据,从而极大降低内存占用和渲染开销,是保证长列表流畅滚动的首选方案。
-
实现高性能列表滚动的关键实践:
- 必须使用LazyForEach:这是处理海量数据列表的基础。请确保数据源实现
IDataSource接口,并正确实现totalCount()、getData()和registerDataChangeListener()等方法。 - 优化列表项组件:
- 保持列表项UI结构尽可能简单扁平,减少嵌套。
- 使用
@Reusable装饰器装饰可复用的自定义组件,进一步提升LazyForEach的组件复用效率。 - 对于复杂列表项,考虑将图片等资源的加载异步化或使用懒加载。
- 优化数据更新:
- 使用
List或Array作为数据源时,应避免在getData()方法中进行耗时操作。 - 当数据变化时,通过
IDataSource的notifyDataChange()方法进行精准更新(如notifyDataChange({ index: 修改项的起始索引 })),而非通知整个列表刷新。
- 使用
- 合理使用列表容器:
List组件本身已针对滚动性能进行优化。确保为其设置固定的宽高或使用弹性布局占满空间。- 可配合
scrollToIndex等方法实现快速定位。
- 必须使用LazyForEach:这是处理海量数据列表的基础。请确保数据源实现
总结:要避免卡顿,关键在于使用LazyForEach实现组件的按需创建与复用,并配合简化组件结构与精准数据更新。对于静态小数据集可用ForEach,但对于“大量数据”,LazyForEach是必须的。

