鴻蒙 LazyForEach 通用數(shù)據(jù)源實(shí)現(xiàn)

前言

鴻蒙中的通過(guò)ForEach循環(huán)渲染會(huì)一次性創(chuàng)建出所有子組件,即便不在可視區(qū)域外,而LazyForEach則可以進(jìn)行按需迭代數(shù)據(jù),當(dāng)組件滑出可視區(qū)域外時(shí),框架會(huì)進(jìn)行組件銷毀回收以降低內(nèi)存占用。而LazyForEach需要開(kāi)發(fā)者實(shí)現(xiàn)IDataSource數(shù)據(jù)源相關(guān)接口。

封裝通用數(shù)據(jù)源類

我們可以封裝一個(gè)類BasicDataSource,遵循IDataSource并實(shí)現(xiàn)相關(guān)接口,同時(shí)我們可以提供相關(guān)的數(shù)據(jù)操作方法,以便調(diào)用方在操作數(shù)據(jù)后可以通知組件進(jìn)行數(shù)據(jù)重新渲染。

以下為BasicDataSource類的實(shí)現(xiàn):

/**
 * LazyForEach 基礎(chǔ)數(shù)據(jù)源
 */
export class BasicDataSource<T> implements IDataSource {
  // 監(jiān)聽(tīng)器
  private listeners: DataChangeListener[] = [];
  // 數(shù)據(jù)數(shù)組
  private dataArray: T[]

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

  // MARK: - IDataSource

  // 總條數(shù)
  totalCount(): number {
    return this.dataArray.length
  }

  // 獲取某條數(shù)據(jù)
  getData(index: number): T {
    return this.dataArray[index]
  }

  // 該方法為框架側(cè)調(diào)用,為L(zhǎng)azyForEach組件向其數(shù)據(jù)源處添加listener監(jiān)聽(tīng)
  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.includes(listener)) {
      return
    }
    this.listeners.push(listener)
  }

  // 該方法為框架側(cè)調(diào)用,為對(duì)應(yīng)的LazyForEach組件在數(shù)據(jù)源處去除listener監(jiān)聽(tīng)
  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener)
    if (pos >= 0) {
      this.listeners.splice(pos, 1)
    }
  }

  // MARK: - Public Methods

  /**
   * 刷新數(shù)據(jù)
   */
  public reloadData(): void {
    this.notifyDataReloaded()
  }

  /**
   * 查:獲取所有數(shù)據(jù)
   * @returns 數(shù)據(jù)數(shù)組
   */
  public getAllArray(): T[] {
    return this.dataArray
  }

  /**
   * 增:添加單條數(shù)據(jù)
   * @param data 單條數(shù)據(jù)
   */
  public addData(data: T): void {
    this.dataArray.push(data)
    this.notifyDataAdd(this.dataArray.length - 1);
  }

  /**
   * 增:添加多條數(shù)據(jù)
   * @param dataArray 多條數(shù)據(jù)
   */
  public addDataArray(dataArray: T[]): void {
    dataArray.forEach((data) => {
      this.dataArray.push(data)
    })
    this.notifyDataAdd(this.dataArray.length - 1);
  }

  /**
   * 增:在某個(gè)位置插入數(shù)據(jù)
   * @param data 要插入的數(shù)據(jù)
   * @param index 插入位置
   */
  public insertData(data: T, index: number) {
    this.dataArray.splice(index, 0, data)
    this.notifyDataAdd(index);
  }

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

  /**
   * 改:交換數(shù)據(jù)位置
   * @param from 起始位置
   * @param to 目標(biāo)位置
   */
  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)
  }

  /**
   * 改:重置數(shù)據(jù)
   * @param dataArray 新的數(shù)據(jù)數(shù)組
   */
  public resetData(dataArray: T[]): void {
    this.dataArray = dataArray
    this.notifyDataReloaded()
  }

  /**
   * 刪:刪除指定索引的數(shù)據(jù)
   * @param index 索引
   */
  public deleteData(index: number): void {
    this.dataArray.splice(index, 1)
    this.notifyDataDelete(index)
  }


  // MARK: - Private Methods

  /**
   * 通知組件重新加載所有數(shù)據(jù)
   */
  private notifyDataReloaded(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    })
  }

  /**
   * 通知組件index的位置有數(shù)據(jù)添加
   * @param index 當(dāng)前傳入的索引值
   */
  private notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    })
  }

  /**
   * 通知組件數(shù)據(jù)有移動(dòng)。將from和to位置的數(shù)據(jù)進(jìn)行交換
   * @param from
   * @param to
   */
  private notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to) // 系統(tǒng) api 命名不嚴(yán)謹(jǐn)
    })
  }

  /**
   * 通知組件刪除index位置的數(shù)據(jù)并刷新LazyForEach的展示內(nèi)容
   */
  private notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index)
    })
  }

  /**
   *  通知組件index的位置有數(shù)據(jù)有變化
   */
  private notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index);
    })
  }
}

使用示例

// 復(fù)用組件
@Reusable
@Component
export struct CardView {
  // 被\@State修飾的變量item才能更新,未被\@State修飾的變量不會(huì)更新。
  @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}條數(shù)據(jù)`)
        })
      }

      Row({ space: 5 }) {
        TextInput({
          placeholder: '輸入刪除數(shù)據(jù)下標(biāo)'
        })
          .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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容