Read the original article:The scrollbar size changes when a List nests a LazyForEach
Problem Description
When using LazyForEach to load multiple ListItemGroups and their internal items, the scrollbar may experience length changes during scrolling.
Background Knowladge
- ForEach loads the entire dataset into a List at once. Since the scroll height of the interface is predetermined, the size of the scrollbar does not change. However, when dealing with large volumes of data, this approach can lead to high memory usage and slow page loading.
- LazyForEach adopts a lazy loading strategy, loading a small amount of data based on the visible range of the interface, and gradually loading new data as the List scrolls.
Solution
When using nested LazyForEach structures (with the outer layer loading ListItemGroup and the inner layer loading ListItem), due to the lazy loading feature of LazyForEach, the scrollbar length is dynamically calculated based on the number of ListItem's currently loaded within the visible s, rather than the total number of items across all ListItemGroup's. This results in frequent changes in the scrollbar size during scrolling, which negatively affects the visual experience.
A hybrid rendering strategy can be used:
- The outer layer uses ForEach to load ListItemGroup in one go, ensuring that the total length of the scrollbar is calculated based on the complete number of list items.
- The inner layer uses LazyForEach to load ListItem on demand, implementing lazy loading of child items within ListItemGroup to maintain performance optimization advantages.
Use ForEach to load two ListItemGroups, with the first ListItemGroup lazily loading 30 ListItems and the second ListItemGroup lazily loading 350 ListItems.
Code Snippet / Configuration
@Entry
@Component
struct MultiGroupListDemo {
private groupDataSource: GroupDataSource = new GroupDataSource() // Data Source
// Initialize data
aboutToAppear() {
const group1 = new GroupItem(`Group 1`);
for (let itemIndex = 1; itemIndex <= 30; itemIndex++) {
group1.addItem(`Group 1 - Item${itemIndex}`);
}
this.groupDataSource.addGroup(group1);
const group2 = new GroupItem(`Group 2`);
for (let itemIndex = 1; itemIndex <= 350; itemIndex++) {
group2.addItem(`Group 2 - Item${itemIndex}`);
}
this.groupDataSource.addGroup(group2);
}
// Group header builder
@Builder
groupHeader(title: string) {
Text(title)
.fontSize(20)
.backgroundColor(Color.White)
.width('100%')
.padding(10)
}
build() {
Column() {
List({ space: 10 }) {
ForEach(this.groupDataSource.groups, (group: GroupItem) => {
ListItemGroup({ header: this.groupHeader(group.title) }) {
LazyForEach(group.itemSource, (item: string) => {
ListItem() {
Text(item)
.fontSize(18)
.width('100%')
.height(60)
.textAlign(TextAlign.Center)
.backgroundColor(Color.White)
}
}, (item: string) => item) // Use the content itself as the unique identifier
}
.divider({ strokeWidth: 1, color: Color.Gray })
}, (group: GroupItem) => group.title) // Use group titles as unique identifiers
}.width('100%').height('100%')
}.backgroundColor(0xF5F5F5)
}
}Copy codeCopy code
// Group Data Source Implementation
class GroupItem {
title: string;
itemSource: ItemDataSource;
constructor(title: string) {
this.title = title;
this.itemSource = new ItemDataSource();
}
addItem(item: string) {
this.itemSource.pushData(item);
}
}
// Master Data Source Class
class GroupDataSource implements IDataSource {
groups: GroupItem[] = [];
private listeners: DataChangeListener[] = [];
addGroup(group: GroupItem) {
this.groups.push(group);
this.notifyDataReload();
}
totalCount(): number {
return this.groups.length;
}
getData(index: number): GroupItem {
return this.groups[index];
}
registerDataChangeListener(listener: DataChangeListener): void {
this.listeners.push(listener);
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const index = this.listeners.indexOf(listener);
if (index >= 0) {
this.listeners.splice(index, 1);
}
}
private notifyDataReload() {
this.listeners.forEach(listener => listener.onDataReloaded());
}
}
//Sub-item Data Source Class
class ItemDataSource implements IDataSource {
private items: string[] = [];
private listeners: DataChangeListener[] = [];
pushData(item: string) {
this.items.push(item);
this.notifyDataAdd(this.items.length - 1);
}
totalCount(): number {
return this.items.length;
}
getData(index: number): string {
return this.items[index];
}
registerDataChangeListener(listener: DataChangeListener): void {
this.listeners.push(listener);
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const index = this.listeners.indexOf(listener);
if (index >= 0) {
this.listeners.splice(index, 1);
}
}
private notifyDataAdd(index: number) {
this.listeners.forEach(listener => listener.onDataAdd(index));
}
}
Limitations or Considerations
- This example supports API Version 19 Release and above.
- This example supports HarmonyOS 5.1.1 Release SDK and above.
- This example requires DevEco Studio 5.1.1 Release and above for compilation and execution.
Top comments (0)