HarmonyOS 鸿蒙Next中大量数据全量加载卡顿,后续加载导致checkboxgroup选择异常
HarmonyOS 鸿蒙Next中大量数据全量加载卡顿,后续加载导致checkboxgroup选择异常 现在有5000条json对象数据(数量不可控可能更多),按照50一组分组(每组点击可折叠或展示)。
利用foreach全量加载页面会极其卡顿甚至闪退,一进页面立马点击全选,随着后续加载该选择状态会异常甚至每次点击不能立即反应
使用懒加载也会卡顿(感觉卡顿比foreach更严重,foreach只是首次全量加载卡顿,懒加载全程都在卡),同时懒加载的组件回收机制也会导致全选状态异常
请问该如何实现顺畅的全量加载并且不影响全选状态?
(由于checkboxgroup不能多层嵌套,我是讲每一个小分组包一个checkgroup,最大的全选状态通过观察每个小分组的全选状态进行遍历来确定)
更多关于HarmonyOS 鸿蒙Next中大量数据全量加载卡顿,后续加载导致checkboxgroup选择异常的实战教程也可以访问 https://www.itying.com/category-93-b0.html
尊敬的开发者,您好,
长列表作为应用开发中最常见的开发场景之一,通常会包含成千上万个列表项,在此场景下,直接使用循环渲染ForEach一次性加载所有的列表项,会导致渲染时间过长,影响用户体验。而使用数据懒加载LazyForEach替换循环渲染ForEach,可以按需加载列表项,从而提升列表性能。数据懒加载的示例代码可以参考LazyForEach。
虽然,按需加载列表项可以优化长列表性能,但在快速滑动长列表的场景下,可能会来不及加载需要显示的列表项,导致出现白块的现象,从而影响用户体验。而在ArkUI中,List容器提供了cachedCount属性,LazyForEach可以结合cachedCount属性一起使用,能够避免白块的现象。cachedCount可以设置列表中ListItem/ListItemGroup的预加载数量,并且只在LazyForEach中生效,即cachedCount只能与LazyForEach一起使用。除了List容器,其他容器Grid、Swiper以及WaterFlow也都包含cachedCount属性。cachedCount的使用方法如下所示。
List() {
// ...
}.cachedCount(3)
此外,HarmonyOS应用框架提供了组件复用能力,可以结合LazyForEach一起使用,进一步优化长列表的性能。组件复用会把组件树上将要移除的组件进行回收,回收的组件会进入到一个回收缓存区。后续创建新组件节点时,会复用缓存区中的节点,节约组件重新创建的时间。关于组件复用的详细原理可以参考组件复用。针对长列表加载的性能优化,可以参考优化长列表加载慢丢帧问题。
示例代码如下:
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = [];
private originDataArray: Array<TimeTable> | Array<CheckboxItemData> = [];
public totalCount(): number {
return 0;
}
public getData(index: number): TimeTable | CheckboxItemData {
return this.originDataArray[index];
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener');
this.listeners.push(listener);
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener');
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);
})
}
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to);
})
}
}
class MyDataSource extends BasicDataSource {
private dataArray: TimeTable[] | Array<CheckboxItemData> = [];
constructor(data: Array<TimeTable> | Array<CheckboxItemData>) {
super();
this.dataArray = data;
}
public totalCount(): number {
return this.dataArray.length;
}
public getData(index: number): TimeTable | CheckboxItemData {
return this.dataArray[index];
}
public addDataTimeTable(index: number, data: TimeTable): void {
this.dataArray.splice(index, 0, data);
this.notifyDataAdd(index);
}
public addDataCheckboxItemData(index: number, data: CheckboxItemData): void {
this.dataArray.splice(index, 0, data);
this.notifyDataAdd(index);
}
public pushDataTimeTable(data: TimeTable): void {
(this.dataArray as Array<TimeTable>).push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
public pushDataCheckboxItemData(data: CheckboxItemData): void {
(this.dataArray as Array<CheckboxItemData>).push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
public operateCheckboxItemData(isSelect: boolean): void {
(this.dataArray as Array<CheckboxItemData>).forEach((item) => {
item.isCheck = isSelect
})
this.notifyDataReload()
}
public operateCheckboxItem(isSelect: boolean, index: number): void {
(this.dataArray as Array<CheckboxItemData>)[index].isCheck = isSelect
this.notifyDataChange(index)
}
public getDataSource(): CheckboxItemData[] {
return this.dataArray as CheckboxItemData[]
}
}
/*
* The course schedule data structure, title represents the day of the week, and projects represents the list of courses for that day
*/
@Observed
class CheckboxItemData{
isCheck: boolean = false;
itemName: string = '';
constructor(name:string) {
this.itemName = name
}
}
@Observed
class TimeTable {
title: string = ''
data: CheckboxItemData[] = [];
constructor(til:string) {
this.title = til
}
}
@Entry
@Component
export struct Index {
@State timeTable: TimeTable[] = [];
private data1: MyDataSource = new MyDataSource([]);
@Provide isSelectAllAll:boolean = false
aboutToAppear(): void {
for (let index = 0; index < 1000; index++) {
this.timeTable.push(new TimeTable(`第${index+1}组`))
for(let j = 0;j<50;j++){
this.timeTable[index].data.push(new CheckboxItemData(`checkbox-${index+1}-${j}`))
}
this.data1.pushDataTimeTable(this.timeTable[index])
}
}
build() {
Column(){
Button(this.isSelectAllAll?'全不选':'全选')
.onClick(()=>{
this.isSelectAllAll = !this.isSelectAllAll
})
List({ space: 3 }) {
// ForEach(this.timeTable,(item: TimeTable)=>{
// ListItem() {
// Text(item.title)
// .width('100%')
// .height(100)
// .fontSize(20)
// .textAlign(TextAlign.Center)
// .backgroundColor(0xFFFFFF)
// }
// },(item: TimeTable) => item.title)
LazyForEach(this.data1, (item: TimeTable) => {
ListItem() {
CheckGroupComp({data:item})
}
}, (item: TimeTable) => item.title)
}
.cachedCount(5)
.width('100%')
.height('100%')
}
}
}
@Component
struct CheckGroupComp{
@ObjectLink data:TimeTable
private data2: MyDataSource = new MyDataSource([]);
@State isSelectAll: boolean = false
@Watch('selectAll') @Consume isSelectAllAll:boolean
selectAll(){
this.data2.operateCheckboxItemData(this.isSelectAllAll)
this.isSelectAll = this.isSelectAllAll
}
aboutToAppear(): void {
if(this.data){
this.data.data.forEach(it=>{
this.data2.pushDataCheckboxItemData(it)
})
}
}
build() {
Column() {
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
CheckboxGroup({ group: this.data.title+"group" })
.selectAll(this.isSelectAll)
.hitTestBehavior(HitTestMode.None)
Text(`${this.data.title}全选`).fontSize(25).padding(12)
}.onClick(() => {
this.isSelectAll = !this.isSelectAll
this.data2.operateCheckboxItemData(this.isSelectAll)
}).padding({ left: 10 })
List({ space: 3 }) {
LazyForEach(this.data2, (item: CheckboxItemData, index: number) => {
ListItem() {
Row() {
Checkbox({ name: `checkbox-${item}` })
.select(item.isCheck)
.onChange((value: boolean) => {
this.data2.operateCheckboxItem(value, index)
let dataSource = this.data2.getDataSource()
this.isSelectAll = dataSource.every((item) => item.isCheck === true)
})
Text(item.itemName).fontSize(20)
}.margin({ left: 10, right: 10 })
}
}, (item: CheckboxItemData) => item.itemName + item.isCheck)
}
.height(300)
.cachedCount(5)
}
}
}
如果还是不能解决您的问题,麻烦您提供下能复现问题的最小demo吧。
更多关于HarmonyOS 鸿蒙Next中大量数据全量加载卡顿,后续加载导致checkboxgroup选择异常的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
不能用外部三方库和ComponentV2
方便提供下demo看看嘛?
找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17
不一定采用CheckBoxGroup的方式去做可以使用list的方式自己模拟
毋庸置疑采用懒加载,是否可以提供ui图看看,懒加载是否成功您这边有测试过吗?
懒加载有数据,就是上下滑动会非常卡顿
HarmonyOS 鸿蒙Next大量数据全量加载卡顿是因一次性创建所有组件导致主线程阻塞,应使用LazyForEach实现按需加载。checkboxgroup选择异常通常源于数据源更新后未正确维护选中状态,或列表回收时checkbox实例状态丢失,需确保数据源与选择状态绑定并随列表刷新正确同步。
针对大数据量分组列表卡顿和全选状态异常,核心解决思路是:只渲染可见内容 + 集中管理选中状态。
方案简述
- 数据展示:列表使用
List+LazyForEach,仅对当前展开的分组渲染其下50条数据;折叠的分组不渲染子项,仅渲染分组头。这样实际屏幕上仅需加载极少量的 Item 节点,大幅减少首次渲染和滚动压力。 - 选中状态管理:用一个大数组(或 Map)
selectedMap集中存储每条数据的勾选状态(例如 index 或 id 为 key)。每个分组的CheckboxGroup通过@Watch监听该分组的子项选择变化,直接修改selectedMap;全选则是遍历所有子项批量修改selectedMap,驱动 UI 更新。 - 避免组件回收丢失状态:
LazyForEach回收组件时,通过数据源的方法onMoveToCache或直接忽略,因为勾选状态已保存在selectedMap中,再次渲染时会重新读取正确状态。
关键代码片段
// 全局选中状态 Map
@State selectedMap: Map<number, boolean> = new Map();
// 数据源模型
class DataItem {
id: number;
name: string;
// ...
}
// 分组模型
class Group {
title: string;
items: DataItem[];
isExpanded: boolean = false;
}
// 构建 List
List() {
LazyForEach(this.dataSource, (group: Group, groupIndex: number) => {
ListItem() {
Column() {
// 分组头,点击展开/折叠
Text(group.title)
.onClick(() => {
group.isExpanded = !group.isExpanded;
this.dataSource.notifyDataChange(groupIndex); // 刷新视图
})
// 仅展开时渲染子项
if (group.isExpanded) {
ForEach(group.items, (item: DataItem, itemIndex: number) => {
Checkbox({ name: `item_${groupIndex}_${itemIndex}`, group: `group_${groupIndex}` })
.select(this.selectedMap.get(item.id) ?? false)
.onChange((value: boolean) => {
this.selectedMap.set(item.id, value);
// 可在此处检查分组全选状态并更新分组 Checkbox 标识
})
})
}
}
}
})
}
核心要点
- 只渲染当前展开的分组,未展开的分组只占一行,5000 条数据最多渲染一两个分组(不到 100 个 item),全程流畅。
- 勾选状态与视图解耦,状态存在
selectedMap中,不受组件回收影响;展开新分组时重新读取selectedMap显示正确状态。 - 全选操作:直接遍历所有需要选中的 item id,写入
selectedMap,然后通知对应可见分组刷新即可,无性能压力。
采用此方案,5000 条乃至更多数据都可丝滑操作,全选状态也不会因组件回收而错乱。


