鸿蒙开发之封装懒加载list组件(API12)

152 阅读4分钟

1. List组件要完成的功能

● 支持下拉刷新
● 支持上拉加载
● 支持自定义结构
● 支持传入数据渲染内容
● 支持设置加载提示文本
● 支持设置加载完成文本
● 支持控制是否显示加载的进度条

2. 基础list组件实现

2.1. 实现列表懒加载

  • 懒加载list指南
  • 支持自定义结构 -- 设置renderItem 构建函数,后期使用组件可传入item结构
@BuilderParam
renderItem: (item: object) => void // 负责渲染每一项的结构
  
LazyForEach(this.lazyDataSource, (item: object, index: number) => {
  ListItem() {
    if (this.renderItem) {
      this.renderItem(item)
    }
  }
})
  • 使用懒加载,list数据源必须采用 lazyDataSource结构类定义
  • 创建 ListDataSource.ets 文件
    • 创建BasicDataSource类 继承 IDataSource(官方定义)
    • 创建ListDataSource类 继承 BasicDataSource,创建操作数据类
// Basic implementation of IDataSource to handle data listener
export class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private originDataArray: object[] = [];

  public totalCount(): number {
    return 0;
  }

  public getData(index: number): object {
    return this.originDataArray[index];
  }

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

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

  // 通知LazyForEach组件需要重载所有子组件
  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    })
  }

  // 通知LazyForEach组件需要在index对应索引处添加子组件
  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    })
  }

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

  // 通知LazyForEach组件需要在index对应索引处删除该子组件
  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index);
    })
  }

  // 通知LazyForEach组件将from索引和to索引处的子组件进行交换
  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to);
    })
  }
}

// 封装自己的dataSource
export class ListDataSource extends BasicDataSource {
  private dataArray: object[] = [];

  public totalCount(): number {
    return this.dataArray.length;
  }

  public getData(index: number): object {
    return this.dataArray[index];
  }

  public addData(index: number, data: object): void {
    this.dataArray.splice(index, 0, data);
    this.notifyDataAdd(index);
  }

  public pushData(data: object): void {
    this.dataArray.push(data);
    this.notifyDataAdd(this.dataArray.length - 1);
  }

  public reloadData(data: object[]): void {
    this.dataArray = data // 直接赋值
    this.notifyDataReload()
    // this.dataArray.push(data);
    // this.notifyDataAdd(this.dataArray.length - 1);
  }
}
  • 此时里list组件的ui结构如下
import { ListDataSource } from './ListDataSource'

@Component
  export struct ListComponent {
    @State
    lazyDataSource:ListDataSource= new ListDataSource()
    @BuilderParam
    renderItem: (item: object) => void // 负责渲染每一项的结构

    build() {
      List() {
        LazyForEach(this.lazyDataSource, (item: object, index: number) => {
          ListItem() {
            if (this.renderItem) {
              this.renderItem(item)
            }
          }
        })
      }
    }
  }

2.2. 设置底部加载文本

  • 根据变量判断底部是否正在加载还是已经加载完毕,显示对应的文字提醒信息
@State
finished: boolean = false // 是否已经加载结束
@State
loading: boolean = false // 是否正在加载  用来控制阀门
loadingText: string = "拼命加载中..." // 底部的加载提示文本
finishText: string = "没有拉" // 加载完成的提示文本

@Builder
getBottomBuilder() {
  Row({ space: 10 }) {
    // 判断
    if (this.finished) {
      Text(this.finishText)
        .fontSize(14)
        .fontColor($r("app.color.text_secondary"))
    } else {
      if (this.loading) {
        Text(this.loadingText)
          .fontSize(14)
          .fontColor($r("app.color.text_secondary"))
        LoadingProgress()
          .width(20)
          .aspectRatio(1)
          .color($r("app.color.text_secondary"))
      }
    }
  }
  .width("100%")
  .justifyContent(FlexAlign.Center)
  .height(50)
}

List() {
  LazyForEach(this.lazyDataSource, (item: object, index: number) => {
    .....
  })
  // 底部提示
  ListItem() {
    // 写一个builder
    this.getBottomBuilder()
  }
}

2.3. 支持上拉加载

  • 当list 组件滑动到最底部时触发 onReachEnd 事件,我们在这里面写上执行下拉加载
// 上拉加载
onLoad: () => void = () => {
}
  
.onReachEnd(async () => {
  // 上拉加载
  if (!this.loading && !this.finished) {
    // 现在没有加载 并且明确还有数据 才可以进来
    this.loading = true // 保证当前只有自己在
    await this.onLoad()
    this.loading = false
  }
})

2.4. 支持下拉刷新

  • 下拉刷新我们需要在最外层包裹一个Refresh组件,里面添加参数
  • LoadingProgress() 为加载组件,当我们触发下拉刷新是,可以显示加载效果,加载完毕则隐藏
@State
refreshIng: boolean = false // 控制下拉刷新的变量

@Builder
getRefreshDisplay() {
  // 渲染不同阶段的显示内容
  Row({ space: 10 }) {
    LoadingProgress()
      .width(40)
      .aspectRatio(1)
      .color($r("app.color.primary"))
    Text(this.getStatusText())
      .fontSize(14)
      .fontColor($r("app.color.text_secondary"))
  }
  .justifyContent(FlexAlign.Center)
  .width("100%")
  .height(50)

}

build() {
  Refresh({ refreshing: $$this.refreshIng, builder: this.getRefreshDisplay }) {
    List() {
      ...
    }
  }
}

3. 源代码

  • ListComponent.ets
import { promptAction } from '@kit.ArkUI'
import { ListDataSource } from './ListDataSource'

@Component
export struct ListComponent {
  @State
  refreshStatus: RefreshStatus = RefreshStatus.Inactive // 用来记录下拉的状态
  @State
  refreshIng: boolean = false // 控制下拉刷新的变量
  // 到底还有没有下一页
  @Prop
  finished: boolean = false // 是否已经加载结束
  @Prop
  @Watch("updateDataSource")
  dataSource: object[] = [] // 数组
  @BuilderParam
  renderItem: (item: object) => void // 负责渲染每一项的结构
  // 上拉加载
  onLoad: () => void = () => {
  }
  // 下拉刷新
  onRefresh: () => void = () => {
  }
  loadingText: string = "拼命加载中..." // 底部的加载提示文本
  finishText: string = "没有拉" // 加载完成的提示文本
  @State
  loading: boolean = false // 是否正在加载  用来控制阀门
  lazyDataSource: ListDataSource = new ListDataSource()

  updateDataSource() {
    this.lazyDataSource.reloadData(this.dataSource) // 重新加载数据
  }

  @Builder
  getBottomBuilder() {
    Row({ space: 10 }) {
      // 判断
      if (this.finished) {
        Text(this.finishText)
          .fontSize(14)
          .fontColor($r("app.color.text_secondary"))
      } else {
        if (this.loading) {
          Text(this.loadingText)
            .fontSize(14)
            .fontColor($r("app.color.text_secondary"))
          LoadingProgress()
            .width(20)
            .aspectRatio(1)
            .color($r("app.color.text_secondary"))
        }
      }
    }
    .width("100%")
    .justifyContent(FlexAlign.Center)
    .height(50)
  }

  // 根据不同的状态获取不同的文本
  getStatusText() {
    switch (this.refreshStatus) {
      case RefreshStatus.Drag:
        return "继续下拉"
      case RefreshStatus.OverDrag:
        return "松手加载"
      case RefreshStatus.Refresh:
        return "加载中"
    }
    return ""
  }

  @Builder
  getRefreshDisplay() {
    // 渲染不同阶段的显示内容
    Row({ space: 10 }) {
      LoadingProgress()
        .width(40)
        .aspectRatio(1)
        .color($r("app.color.primary"))
      Text(this.getStatusText())
        .fontSize(14)
        .fontColor($r("app.color.text_secondary"))
    }
    .justifyContent(FlexAlign.Center)
    .width("100%")
    .height(50)

  }

  build() {
    Refresh({ refreshing: $$this.refreshIng, builder: this.getRefreshDisplay }) {
      List() {
        LazyForEach(this.lazyDataSource, (item: object, index: number) => {
          ListItem() {
            if (this.renderItem) {
              this.renderItem(item)
            }
          }
        })
        // 底部提示
        ListItem() {
          // 写一个builder
          this.getBottomBuilder()
        }

      }
      .onReachEnd(async () => {
        // 上拉加载
        // 阀门
        if (!this.loading && !this.finished) {
          // 现在没有加载 并且明确还有数据 才可以进来
          this.loading = true // 保证当前只有自己在
          await this.onLoad()
          this.loading = false
        }
      })
    }
    .onStateChange(async (state) => {
      this.refreshStatus = state // 记录当下的状态
      if (state === RefreshStatus.Refresh) {
        // 松手时触发
        await this.onRefresh() // 刷新 意味者全部重来
        this.refreshIng = false // 手动关闭进度
        this.loading = false // 关闭loading  之前的东西重新设置

        sound.playAudio("sound.mp3")
      }

    })
  }
}
  • ListDataSource.ets
// Basic implementation of IDataSource to handle data listener
export class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private originDataArray: object[] = [];

  public totalCount(): number {
    return 0;
  }

  public getData(index: number): object {
    return this.originDataArray[index];
  }

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

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

  // 通知LazyForEach组件需要重载所有子组件
  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    })
  }

  // 通知LazyForEach组件需要在index对应索引处添加子组件
  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    })
  }

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

  // 通知LazyForEach组件需要在index对应索引处删除该子组件
  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index);
    })
  }

  // 通知LazyForEach组件将from索引和to索引处的子组件进行交换
  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to);
    })
  }
}

// 封装自己的dataSource
export class ListDataSource extends BasicDataSource {
  private dataArray: object[] = [];

  public totalCount(): number {
    return this.dataArray.length;
  }

  public getData(index: number): object {
    return this.dataArray[index];
  }

  public addData(index: number, data: object): void {
    this.dataArray.splice(index, 0, data);
    this.notifyDataAdd(index);
  }

  public pushData(data: object): void {
    this.dataArray.push(data);
    this.notifyDataAdd(this.dataArray.length - 1);
  }

  public reloadData(data: object[]): void {
    this.dataArray = data // 直接赋值
    this.notifyDataReload()
    // this.dataArray.push(data);
    // this.notifyDataAdd(this.dataArray.length - 1);
  }
}