HarmonyOS 鸿蒙Next下实现分页预加载功能

HarmonyOS 鸿蒙Next下实现分页预加载功能

ExperimentalPaging

简介

参考Android paging3的设计,实现了HarmonyOS下的paging分页加载

  • 当用户滚动到现有数据的末尾时,自动请求下一页;
  • 跟踪获取前一页或后一页所需要的参数;
  • 跟踪加载状态,并支持List、Gird、WaterFlow。为失败的加载提供简便的重试功能;
  • 支持状态管理V1和状态管理V2

安装

ohpm install @jackiehou/experimental-paging 

使用说明

import { LoadParams, LoadResult, LoadState, PagingSource } from '@jackiehou/experimental-paging'

继承PagingSource,如果使用LazyForEach则还需要implements IDataSource接口,使用Repeat则无需implements IDataSource

const PAGE_SIZE = 20 //一页的item的个数

//继承PagingSource并实现LazyForEach的IDataSource接口,如果使用Repeat则无需implements IDataSource
class PagingDataSource extends PagingSource<number, ItemData> implements IDataSource {
  private listeners: DataChangeListener[] = []; //如果使用Repeat,则无需使用DataChangeListener
  public array: Array<ItemData> = [];

  constructor(array?: Array<ItemData>) {
    /* 第一个参数 pageSize           一页的个数
     * 第二个参数 prefetchDistance  当滑动到末尾/头部小于prefetchDistance个item的时候,触发加载,默认值pageSize乘以2
     * 第三个个参数 initialLoadSize  初始化第一页请求的个数,默认值pageSize乘以3
     */
    super(PAGE_SIZE, PAGE_SIZE * 2, PAGE_SIZE * 3)
    this.array = array ? array : []
  }

  //PagingSource会调用此方法,在这里需要更新全部数据
  reloadData(items: ItemData[]): void {
    this.array.splice(0, this.totalCount(), ...items)
    this.notifyDataReload() //如果使用Repeat,则无需调用notifyDataReload
  }

  //PagingSource会调用此方法,在这里添加上、下一页的数据
  batchAdd(startIndex: number, items: ItemData[]): void {
    this.array.splice(startIndex, 0, ...items)
    //如果使用Repeat,则无需调用notifyDatasetChange
    this.notifyDatasetChange([{
      type: DataOperationType.ADD,
      index: startIndex,
      count: items.length,
      key: items.map(item => JSON.stringify(item))
    }])
  }

  //PagingSource会调用此方法,在这里调用服务器接口获取分页数据
  async load(loadParams: LoadParams<number>): Promise<LoadResult<number, ItemData>> {
    //当前页的请求参数,如果loadParams.key为undefined表示初始化加载
    const key = loadParams.key !== undefined ? loadParams.key : 1 /*初始化从第1页开始加载数据*/
    try {
      //模拟网络耗时
      await sleep(1000)
      //根据key和loadParams.loadSize得到当前页的数据
      const items = getData((key - 1) * PAGE_SIZE, loadParams.loadSize, key, PAGE_SIZE)
      return {
        state: LoadState.SUCCEED, //加载成功
        prevKey: undefined, //返回上一页的请求的key,undefined表示上一页没有数据
        nextKey: key <= 10 ? key + (loadParams.loadSize / PAGE_SIZE) : undefined, //返回下一页的请求的key,undefined表示下一页没有数据
        data: items //返回的数据
      }
    } catch (e){
      console.log(`load error key = ${key} error = ${JSON.stringify(e)}`)
      return {state: LoadState.ERROR} //返回错误状态
    }
  }

  //PagingSource和IDataSource需要实现的方法,返回总数
  totalCount(): number {
    return this.array.length
  }

  //...省略实现LazyForEach的IDataSource接口需要实现的方法,如果使用的是Repeat则不用考虑
}

LazyForEach

@Component
struct sample {
  pagingSource = new PagingDataSource()
  @State isListAppeared: boolean = false //List组件是否挂载

  aboutToAppear(): void {
    this.pagingSource.refreshData()
  }

  aboutToDisappear(): void {
    this.pagingSource.release()
  }

  build() {
    Stack() {
      if (!this.isListAppeared && this.pagingSource.refresh === LoadState.Loading) {
        LoadingProgress()
          .color(Color.Blue).width('35%').aspectRatio(1)
      } else {
        if (this.pagingSource.refresh !== LoadState.ERROR) {
          List() {
            LazyForEach(this.pagingSource, (item: ItemData) => {
              ListItem() {
                Row() {
                  Column()
                    .height(100)
                    .width(100)
                    .backgroundColor(item.color)
                  Text(item.name).padding(10)
                }.width('100%').padding(5).height(110)
              }
            }, (item: ItemData) => JSON.stringify(item))
          }
          .size({ width: '100%', height: '100%' })
          .padding(15)
          .onScrollIndex((start: number, end: number) => {
            //在这里必须要调用此方法
            this.pagingSource.onVisibleAreaChanged(start, end)
          })
          .onAppear(() => {
            this.isListAppeared = true
          })
          .onDisAppear(() => {
            this.isListAppeared = false
          })
        } else {
          Button('重试').onClick(() => {
            this.pagingSource.refreshData()
          })
        }
      }
    }.width('100%').height('100%')
  }
}

Repeat

@ComponentV2
struct sampleV2 {
  @Local array :Array<ItemData> = []
  pagingSource = new PagingDataSource(this.array)
  @Local isListAppeared: boolean = false //List组件是否挂载

  aboutToAppear(): void {
    this.pagingSource.refreshData()
  }

  aboutToDisappear(): void {
    this.pagingSource.release()
  }

  build() {
    Stack() {
      if (!this.isListAppeared && this.pagingSource.refresh === LoadState.Loading) {
        LoadingProgress()
          .color(Color.Blue).width('35%').aspectRatio(1)
      } else {
        if (this.pagingSource.refresh !== LoadState.ERROR) {
          List() {
            Repeat(this.array)
              .each((ri: RepeatItem<ItemData>) => {
                ListItem() {
                  Row() {
                    Column()
                      .height(100)
                      .width(100)
                      .backgroundColor(ri.item.color)
                    Text(ri.item.name).padding(10)
                  }.width('100%').padding(5).height(110)
                }
              })
              .key((item) => JSON.stringify(item))
          }
          .size({ width: '100%', height: '100%' })
          .padding(15)
          .onScrollIndex((start: number, end: number) => {
            //在这里必须要调用此方法
            this.pagingSource.onVisibleAreaChanged(start, end)
          })
          .onAppear(() => {
            this.isListAppeared = true
          })
          .onDisAppear(() => {
            this.isListAppeared = false
          })
          .scrollBarWidth(15)
        } else {
          Button('重试').onClick(() => {
            this.pagingSource.refreshData()
          })
        }
      }
    }.width('100%').height('100%')
  }
}

上图示例参考 entry


更多关于HarmonyOS 鸿蒙Next下实现分页预加载功能的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

非常好用,学习了

更多关于HarmonyOS 鸿蒙Next下实现分页预加载功能的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中实现分页预加载,可使用LazyForEach组件配合DataPanel。步骤:

  1. 定义数据源并实现IDataSource接口
  2. 创建LazyForEach组件,设置预加载参数preloadCount
  3. aboutToAppear或滚动事件中触发数据预加载
  4. 使用@ObjectLink管理数据状态

关键代码示例:

@Entry
@Component
struct PageList {
  private data: MyDataSource = new MyDataSource()
  
  build() {
    List() {
      LazyForEach(this.data, (item) => {
        ListItem() {
          Text(item.toString())
        }
      }, (item) => item.id.toString())
    }
  }
}

预加载数量通过preloadCount属性控制。

这是一个关于在HarmonyOS Next中使用ExperimentalPaging库实现分页预加载功能的详细示例。从代码来看,该库参考了Android Paging3的设计,提供了以下核心功能:

  1. 自动分页加载:当用户滚动到列表末尾时自动加载下一页数据
  2. 参数跟踪:自动管理前后页的加载参数
  3. 状态管理:支持加载状态跟踪和错误重试
  4. 多种布局支持:兼容List、Grid和WaterFlow布局

关键实现要点包括:

  1. 继承PagingSource类并实现load()方法处理数据加载逻辑
  2. 对于LazyForEach需要额外实现IDataSource接口
  3. 必须调用onVisibleAreaChanged()方法触发预加载
  4. 支持通过refreshData()方法刷新数据

这个实现方案比较完整,既支持传统的LazyForEach方式,也兼容新的Repeat语法,可以作为HarmonyOS分页加载的参考实现。

回到顶部