HarmonyOS鸿蒙Next中IDatasource和List、LazyForeach的使用

HarmonyOS鸿蒙Next中IDatasource和List、LazyForeach的使用

基本信息

// BasicDataSource实现了IDataSource接口,用于管理listener监听,以及通知LazyForEach数据更新
class BasicDataSource<T> implements IDataSource {
  private listeners: DataChangeListener[] = [];

  public totalCount(): number {
    return 0
  }

  public getData(index: number): T | undefined {
    return undefined
  }

  // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      this.listeners.push(listener);
    }
  }

  // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      this.listeners.splice(pos, 1);
    }
  }

  // 通知LazyForEach组件需要重载所有子组件
  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      // listener.onDataReloaded();
      // 写法2:listener.onDatasetChange([{ type: DataOperationType.RELOAD }])
      listener.onDatasetChange([{ type: DataOperationType.RELOAD }])
    });
  }

  // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      // listener.onDataChange(index);
      // 写法2:listener.onDatasetChange([{type: DataOperationType.CHANGE, index: index}]);
      listener.onDatasetChange([{ type: DataOperationType.CHANGE, index: index }]);
    });
  }

  // 通知LazyForEach组件需要在index对应索引处添加子组件
  notifyDataAdd(index: number, count: number): void {
    this.listeners.forEach(listener => {
      // listener.onDataAdd(index);
      // 写法2:listener.onDatasetChange([{type: DataOperationType.ADD, index: index}]);
      listener.onDatasetChange([{ type: DataOperationType.ADD, index: index, count: count }]);
    });
  }

  // 通知LazyForEach组件需要在index对应索引处删除该子组件
  notifyDataDelete(index: number, count: number): void {
    this.listeners.forEach(listener => {
      // listener.onDataDelete(index);
      // 写法2:listener.onDatasetChange([{type: DataOperationType.DELETE, index: index}]);
      listener.onDatasetChange([{ type: DataOperationType.DELETE, index: index, count: count }]);
    });
  }

  // 通知LazyForEach组件将from索引和to索引处的子组件进行交换
  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      // listener.onDataMove(from, to);
      // 写法2:listener.onDatasetChange([{type: DataOperationType.EXCHANGE, index: {start: from, end: to}}]);
      listener.onDatasetChange([{ type: DataOperationType.EXCHANGE, index: { start: from, end: to } }]);
    });
  }
}

@ObservedV2
export default class LazyDataSource<T> extends BasicDataSource<T> {
  dataArray: T[] = []

  // 列表数量
  public totalCount(): number {
    return this.dataArray.length;
  }

  // 获取某一项
  public getData(index: number): T {
    return this.dataArray[index];
  }

  //获取列表数据
  public getDataList(): T[] {
    return this.dataArray;
  }

  // 修改数据
  public setData(index: number, data: T): void {
    this.dataArray[index] = data
    this.notifyDataChange(index)
  }

  //列表尾部添加数据
  public pushData(data: T): void {
    this.dataArray.push(data);
    this.notifyDataAdd(this.dataArray.length - 1, 1);
  }

  // 根据索引添加数据
  public addData(index: number, data: T): void {
    this.dataArray.splice(index, 0, data);
    this.notifyDataAdd(index, 1)
  }

  // 根据索引添加数据
  public addBatch(index: number, data: T[]): void {
    this.dataArray.splice(index, 0, ...data);
    this.notifyDataAdd(index, data.length);
  }

  // 添加
  public pushAll(newData: T[]): void {
    this.dataArray.push(...newData)
    this.notifyDataAdd(this.dataArray.length - 1, newData.length)
  }

  // 删除
  public deleteData(index: number): void {
    this.dataArray.splice(index, 1);
    this.notifyDataDelete(index, 1)
  }

  // 批量删除
  public deleteBatch(index: number, count: number): void {
    this.dataArray.splice(index, count);
    this.notifyDataDelete(index, count);
  }

  // 刷新数据
  public refreshData(newData: T[]): void {
    this.dataArray = newData;
    this.notifyDataReload();
  }
}
@ObservedV2
export default class Item {
 @Trace id: string = "";
 @Trace name: string = "";
}
List({ scroller: this.elementsScroller }) {
  LazyForEach(this.datasource, (item: Item, index) => {
    ListItem() {
      Flex() {
        Flex({
          direction: FlexDirection.Row,
          justifyContent: FlexAlign.SpaceBetween,
          alignItems: ItemAlign.Center
        }) {
          Text(item.id)
            .height(50)
            .fontColor('#7082a7')
            .fontSize(20)
            .width('30%')
            .textAlign(TextAlign.Start)
            .padding({ left: 5 })
            .borderWidth({ right: 1 })
            .borderColor('#b2b9d1')
          Text(item.name)
            .height(50)
            .fontColor('#7082a7')
            .fontSize(20)
            .maxLines(1)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
            .padding({ left: 5 })
            .layoutWeight(1)
        }
      }
    }
  }, (item: Item, index) => item.id)
}
.height(FULL_PERCENT)

问题1:在中间某个位置插入、删除后,LazyForeach第二个参数中的index没有变化。LazyForeach第三个参数key为元素的唯一id。

问题2:refreshData方法,会让Scroller处于底部,会出现list组件滑动不了的情况


更多关于HarmonyOS鸿蒙Next中IDatasource和List、LazyForeach的使用的实战教程也可以访问 https://www.itying.com/category-93-b0.html

5 回复

(item: Item, index) => item.id)

这个id的唯一性你要保证没有问题,不然也会出现删除不了,或者UI没刷新,因为存在多个ID的组件,可以考虑

更多关于HarmonyOS鸿蒙Next中IDatasource和List、LazyForeach的使用的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


  • 名称: 示例名称
  • 描述: 这是一个示例描述,用于展示如何将HTML转换为Markdown格式。
  • 类型: 示例类型
  • 状态: 活动
  • 创建日期: 2023-10-01
  • 更新日期: 2023-10-02

(item: Item) => item.id)

这里也去掉index

// 刷新数据
public refreshData(newData: T[]): void {
  this.dataArray.length = 1; //先清空
  this.dataArray = newData;
  this.notifyDataReload();
}

在HarmonyOS Next中,IDataSource是数据源接口,用于为LazyForEach提供数据。List是数据容器,存储要展示的项目集合。LazyForEach是懒加载组件,仅在项目可见时构建对应组件,提升列表性能。

使用步骤:

  1. 实现IDataSource接口,重写totalCount()和getData()方法
  2. 创建包含数据的List对象
  3. 在UI中使用LazyForEach,绑定数据源和项构建函数

示例代码片段:

class MyDataSource implements IDataSource {
  private dataList: string[] = ['a', 'b', 'c']
  totalCount(): number { return this.dataList.length }
  getData(index: number): string { return this.dataList[index] }
}

@Entry
@Component
struct MyComponent {
  private data = new MyDataSource()
  
  build() {
    LazyForEach(this.data, (item: string) => {
      Text(item)
    })
  }
}

关于问题1:LazyForEach的index参数在数据源变更时不会自动更新,这是预期行为。index仅表示当前渲染时的位置索引,当数据源中间插入/删除数据时,后续项的index不会自动调整。正确的做法是始终使用数据项的唯一id(key)来标识组件,而不是依赖index参数。

关于问题2:refreshData后Scroller位置异常的问题,这是已知的性能优化场景。建议在调用refreshData前记录当前滚动位置,刷新后通过scroller.scrollToIndex恢复位置。示例代码:

// 刷新前记录位置
let firstVisibleIndex = this.elementsScroller.currentOffset().y / itemHeight;
this.datasource.refreshData(newData);
// 刷新后恢复位置
this.elementsScroller.scrollToIndex(firstVisibleIndex);

对于数据源实现,注意到示例中使用了onDatasetChange的统一通知方式,这是推荐的写法。但需要注意在批量操作时(如addBatch)要正确传递count参数,否则可能导致渲染异常。

回到顶部