鸿蒙 LazyForEach 通用数据源实现

前言

鸿蒙中的通过ForEach循环渲染会一次性创建出所有子组件,即便不在可视区域外,而LazyForEach则可以进行按需迭代数据,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。而LazyForEach需要开发者实现IDataSource数据源相关接口。

封装通用数据源类

我们可以封装一个类BasicDataSource,遵循IDataSource并实现相关接口,同时我们可以提供相关的数据操作方法,以便调用方在操作数据后可以通知组件进行数据重新渲染。

以下为BasicDataSource类的实现:

/**
 * LazyForEach 基础数据源
 */
export class BasicDataSource<T> implements IDataSource {
  // 监听器
  private listeners: DataChangeListener[] = [];
  // 数据数组
  private dataArray: T[]

  constructor(dataArray: T[] = []) {
    this.dataArray = dataArray
  }

  // MARK: - IDataSource

  // 总条数
  totalCount(): number {
    return this.dataArray.length
  }

  // 获取某条数据
  getData(index: number): T {
    return this.dataArray[index]
  }

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

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

  // MARK: - Public Methods

  /**
   * 刷新数据
   */
  public reloadData(): void {
    this.notifyDataReloaded()
  }

  /**
   * 查:获取所有数据
   * @returns 数据数组
   */
  public getAllArray(): T[] {
    return this.dataArray
  }

  /**
   * 增:添加单条数据
   * @param data 单条数据
   */
  public addData(data: T): void {
    this.dataArray.push(data)
    this.notifyDataAdd(this.dataArray.length - 1);
  }

  /**
   * 增:添加多条数据
   * @param dataArray 多条数据
   */
  public addDataArray(dataArray: T[]): void {
    dataArray.forEach((data) => {
      this.dataArray.push(data)
    })
    this.notifyDataAdd(this.dataArray.length - 1);
  }

  /**
   * 增:在某个位置插入数据
   * @param data 要插入的数据
   * @param index 插入位置
   */
  public insertData(data: T, index: number) {
    this.dataArray.splice(index, 0, data)
    this.notifyDataAdd(index);
  }

  /**
   * 改:更新某索引下的数据
   * @param data 数据
   * @param index 索引
   */
  public updateData(data: T, index: number): void {
    this.dataArray.splice(index, 1, data)
    this.notifyDataChange(index)
  }

  /**
   * 改:交换数据位置
   * @param from 起始位置
   * @param to 目标位置
   */
  public exchangeData(from: number, to: number): void {
    let fromData = this.dataArray[from]
    let toData = this.dataArray[to]
    this.dataArray[from] = toData
    this.dataArray[to] = fromData
    this.notifyDataMove(from, to)
  }

  /**
   * 改:重置数据
   * @param dataArray 新的数据数组
   */
  public resetData(dataArray: T[]): void {
    this.dataArray = dataArray
    this.notifyDataReloaded()
  }

  /**
   * 删:删除指定索引的数据
   * @param index 索引
   */
  public deleteData(index: number): void {
    this.dataArray.splice(index, 1)
    this.notifyDataDelete(index)
  }


  // MARK: - Private Methods

  /**
   * 通知组件重新加载所有数据
   */
  private notifyDataReloaded(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    })
  }

  /**
   * 通知组件index的位置有数据添加
   * @param index 当前传入的索引值
   */
  private notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    })
  }

  /**
   * 通知组件数据有移动。将from和to位置的数据进行交换
   * @param from
   * @param to
   */
  private notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to) // 系统 api 命名不严谨
    })
  }

  /**
   * 通知组件删除index位置的数据并刷新LazyForEach的展示内容
   */
  private notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index)
    })
  }

  /**
   *  通知组件index的位置有数据有变化
   */
  private notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index);
    })
  }
}

使用示例

// 复用组件
@Reusable
@Component
export struct CardView {
  // 被\@State修饰的变量item才能更新,未被\@State修饰的变量不会更新。
  @State item: string = '';

  aboutToReuse(params: Record<string, Object>): void {
    this.item = params.item as string;
    console.info(`>>>>> aboutToReuse: ${this.item}`)
  }

  aboutToAppear(): void {
    console.info(`>>>>> aboutToAppear: ${this.item}`)
  }

  build() {
    Column() {
      Text(this.item)
        .fontSize(30)
    }
    .width('100%')
    .borderWidth(1)
    .height(100)
  }
}
import { BasicDataSource } from '../classes/BasicDataSource';
import { CardView } from '../components/CardView';

@Entry
@Component
struct Index {
  private dataSource: BasicDataSource<string> = new BasicDataSource<string>()
  private deleteIndex: string = ''

  aboutToAppear() {
    this.reset()
  }

  build() {
    Column() {
      this.TopView()

      List() {
        LazyForEach(this.dataSource, (item: string) => {
          ListItem() {
            CardView({ item: item })
          }
        }, (item: string) => item)
      }
      .width('100%')
      .layoutWeight(1)
      .borderWidth(1)
      .borderColor(Color.Red)
    }
  }

  @Builder
  TopView() {
    Column() {
      Row({ space: 5 }) {
        Button('添加').onClick((event: ClickEvent) => {
          let date = new Date()
          this.dataSource.addData(date.toString())
        })

        Button('更新').onClick((event: ClickEvent) => {
          let date = new Date()
          this.dataSource.updateData(`更新:${date.toString()}`, 0)
        })

        Button('交换 1-3').onClick((event: ClickEvent) => {
          this.dataSource.exchangeData(1, 3)
        })
      }

      Row({ space: 5 }) {
        Button('重置').onClick((event: ClickEvent) => {
          this.reset()
        })

        Button('刷新').onClick((event: ClickEvent) => {
          this.dataSource.reloadData()
        })

        Button('查询').onClick((event: ClickEvent) => {
          let count = this.dataSource.totalCount()
          console.info(`>>>>> 总共${count}条数据`)
        })
      }

      Row({ space: 5 }) {
        TextInput({
          placeholder: '输入删除数据下标'
        })
          .type(InputType.Number)
          .onChange((text) => {
            this.deleteIndex = text
          })
        Button('删除').onClick((event: ClickEvent) => {
          let index = Number(this.deleteIndex)
          if (index >= 0 && index < this.dataSource.totalCount()) {
            this.dataSource.deleteData(index)
          }
        })
      }
      .width(200)
    }
  }

  private reset() {
    let dataArray: string[] = []
    for (let i = 0; i < 100; i++) {
      dataArray.push('原始' + i)
    }
    this.dataSource.resetData(dataArray)
  }
}

参考资料

LazyForEach

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容