HarmonyOS NEXT实战:列表和懒加载

简介: 本教程介绍如何在HarmonyOS应用中实现列表布局与懒加载功能,提升页面性能。通过创建ProductModel模型、BasicDataSource数据源,并结合ListDataSource与LazyForEach组件,实现高效的数据展示与动态加载。适用于教育与实战学习。

HarmonyOS Next实战##HarmonyOS应用开发##教育

目标:实现列表布局,并且通过懒加载加载item项。

前提:需要申请权限ohos.permission.INTERNET。

实现思路:

  1. 创建ProductModel模型
  2. 创建BasicDataSource数据源
  3. 集成BasicDataSource和定制化ListDataSource
  4. 在页面实现LazyForEach循环

LazyForEach使用限制

  • LazyForEach必须在容器组件内使用,仅有List、Grid、Swiper以及WaterFlow组件支持数据懒加载(可配置cachedCount属性,即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。
  • LazyForEach依赖生成的键值判断是否刷新子组件,若键值不发生改变,则无法触发LazyForEach刷新对应的子组件。
  • 容器组件内使用LazyForEach的时候,只能包含一个LazyForEach。以List为例,同时包含ListItem、ForEach、LazyForEach的情形是不推荐的;同时包含多个LazyForEach也是不推荐的。
  • LazyForEach在每次迭代中,必须创建且只允许创建一个子组件;即LazyForEach的子组件生成函数有且只有一个根组件。
  • 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。
  • 允许LazyForEach包含在if/else条件渲染语句中,也允许LazyForEach中出现if/else条件渲染语句。
  • 键值生成器必须针对每个数据生成唯一的值,如果键值相同,将导致键值相同的UI组件渲染出现问题。
  • LazyForEach必须使用DataChangeListener对象进行更新,对第一个参数dataSource重新赋值会异常;dataSource使用状态变量时,状态变量改变不会触发LazyForEach的UI刷新。
  • 为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,需要生成不同于原来的键值来触发组件刷新。
  • LazyForEach必须和@Reusable装饰器一起使用才能触发节点复用。使用方法:将@Reusable装饰在LazyForEach列表的组件上,见使用规则。

ProductModel

export interface ProductModel{
   
  engineerId:string,
  engineerName:string,
  mobile:string,
  avatarImg:string,
  storeId:string,
  storeName:string,
  engineerLevel:string,
  orderNumber:string,
}

BasicDataSource

export class BasicDataSource<T> implements IDataSource {
   
  private listeners: DataChangeListener[] = [];
  private originDataArray: T[] = [];

  public totalCount(): number {
   
    return 0;
  }

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

  registerDataChangeListener(listener: DataChangeListener): void {
   
    if (this.listeners.indexOf(listener) < 0) {
   
      console.info('add listener');
      this.listeners.push(listener);
    }
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
   
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
   
      console.info('remove listener');
      this.listeners.splice(pos, 1);
    }
  }

  notifyDataReload(): void {
   
    this.listeners.forEach(listener => {
   
      listener.onDataReloaded();
    })
  }

  notifyDataAdd(index: number): void {
   
    this.listeners.forEach(listener => {
   
      listener.onDataAdd(index);
    })
  }

  notifyDataChange(index: number): void {
   
    this.listeners.forEach(listener => {
   
      listener.onDataChange(index);
    })
  }

  notifyDataDelete(index: number): void {
   
    this.listeners.forEach(listener => {
   
      listener.onDataDelete(index);
    })
  }

  notifyDataMove(from: number, to: number): void {
   
    this.listeners.forEach(listener => {
   
      listener.onDataMove(from, to);
    })
  }

  notifyDatasetChange(operations: DataOperation[]): void {
   
    this.listeners.forEach(listener => {
   
      listener.onDatasetChange(operations);
    })
  }
}

ListDataSource

import {
    BasicDataSource } from "./BasicDataSource";
import {
    ProductModel } from "./ProductModel";

export class ListDataSource extends BasicDataSource<ProductModel> {
   
  private dataArray: ProductModel[] = [];

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

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

  getAllData():ProductModel[] | null{
   
    return this.dataArray
  }

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

ListDemoPage

import {
    router } from '@kit.ArkUI';
import {
    ListDataSource } from './ListDataSource';
import {
    ProductModel } from './ProductModel';

@Entry
@Component
struct ListDemoPage {
   
  @StorageProp('bottomRectHeight')
  bottomRectHeight: number = 0;
  @StorageProp('topRectHeight')
  topRectHeight: number = 0;
  @State currentPageNum: number = 1
  total: number = 0
  private data: ListDataSource = new ListDataSource();
  isLoading: boolean = false;
  @State loadSuccess: boolean = true

  async aboutToAppear(): Promise<void> {
   
    await this.initData()
  }

  async initData() {
   
      this.isLoading = true;
      await this.listProduct()
      this.isLoading = false;
  }

  async listProduct() {
   
    const param: Param = {
   
      "data": {
    "storeId": 331, "cityId": 320100 },
      "pageNum": this.currentPageNum,
      "pageSize": 10
    }
      //填入模拟数据
      this.total = 20;
      for (let i = 0; i <= 20; i++) {
   
        this.data.pushData({
   
          engineerId: i.toString(),
          engineerName: '小白' + (Math.floor(Math.random() * 100) + 1),
          mobile: '12341234' + i,
          avatarImg: 'https://oss.cloudhubei.com.cn/cms/release/set35/20241014/f3af08b621af0b7c0648c48dcd964000.jpg',
          storeId: 'storeId' + i,
          storeName: 'storeName' + i,
          engineerLevel: '1',
          orderNumber: i.toString(),
        })
      }
  }

  build() {
   
    Column({
    space: 10 }) {
   
      this.header()
      this.content()
    }
    .width('100%')
    .height('100%')
    .padding({
    top: this.topRectHeight })
  }

  @Builder
  header() {
   
    Row() {
   
      Row({
    space: 20 }) {
   
        Image($r('app.media.icon_back'))
          .width(18)
          .height(12)
          .responseRegion([{
   
            x: -9,
            y: -6,
            width: 36,
            height: 24
          }])
        Text('Beauty List')
          .fontWeight(700)
          .fontColor('#525F7F')
          .fontSize(16)
          .lineHeight(22)
      }

      Row({
    space: 6 }) {
   
        SymbolGlyph($r('sys.symbol.clean_fill'))
          .fontSize(18)
          .renderingStrategy(SymbolRenderingStrategy.SINGLE)
          .fontColor([Color.Black])
        Text('清除本地缓存')
          .fontSize(14)
          .fontColor(Color.Black)
      }
      .onClick(() => {
   
        router.replaceUrl({
    url: 'pages/BeautyListPage' })
      })
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .padding({
    left: 20, right: 20 })
  }

  @Builder
  content() {
   
    List() {
   
      LazyForEach(this.data, (item: ProductModel) => {
   
        ListItem() {
   
          Row({
    space: 10 }) {
   
            this.buildImage(item.avatarImg != '' ? item.avatarImg : 'https://oss.cloudhubei.com.cn/cms/release/set35/20241014/f3af08b621af0b7c0648c48dcd964000.jpg')

            Column() {
   
              Text(item.engineerName)
            }
            .layoutWeight(1)
          }
          .width('100%')
          .height(100)
        }
        .borderRadius(4)
        .clip(true)
        .backgroundColor(Color.White)
        .margin({
    right: 20, left: 20, top: 10 })
      }, (item: string) => item)

      ListItem().height(this.bottomRectHeight)
    }
    .width('100%')
    .backgroundColor('#F8F9FE')
    .layoutWeight(1)
    .cachedCount(15)
    .scrollBar(BarState.Off)
    .onReachEnd(async () => {
   
      if (!this.isLoading) {
   
        this.isLoading = true;
        this.currentPageNum++
        await this.listProduct()
        this.isLoading = false;
      }
    })
  }

  @Builder
  buildImage(src:string){
   
    Row() {
   
      if (this.loadSuccess) {
   
        Image(src)
          .width('100%')
          .height('100%')
          .onError(() => {
   
            this.loadSuccess = false
          })
      } else {
   
        Text('图片加载失败...').margin(10).fontColor(Color.Gray)
      }
    }
    .width('50%')
    .height(100)
    .backgroundColor('#eeeeee')
    .justifyContent(FlexAlign.Center)
  }
}

interface Param {
   
  "data": Record<string, number>;
  "pageNum": number;
  "pageSize": number;
}
目录
相关文章
|
11月前
|
监控 JavaScript 编译器
从“天书”到源码:HarmonyOS NEXT 崩溃堆栈解析实战指南
本文详解如何利用 hiAppEvent 监控并获取 sourcemap、debug so 等核心产物,剖析了 hstack 工具如何将混淆的 Native 与 ArkTS 堆栈还原为源码,助力开发者掌握异常分析方法,提升应用稳定性。
1144 97
|
12月前
|
开发者 容器
鸿蒙应用开发从入门到实战(十四):ArkUI组件Column&Row&线性布局
ArkUI提供了丰富的系统组件,用于制作鸿蒙原生应用APP的UI,本文主要讲解Column和Row组件的使用以及线性布局的方法。
890 12
|
12月前
|
JavaScript 开发者 索引
鸿蒙应用开发从入门到实战(九):ArkTS渲染控制
ArkTS拓展了TypeScript,可以结合ArkUI进行渲染控制,是的界面设计具有可编程性。本文简要描述鸿蒙应用开发中的条件渲染和循环渲染。
430 5
|
12月前
|
API 数据处理
鸿蒙应用开发从入门到实战(十三):ArkUI组件Slider&Progress
ArkUI提供了丰富的系统组件,用于制作鸿蒙原生应用APP的UI,本文主要讲解滑块Slider和进度条Progress组件的使用。
450 1
|
11月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1861 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
11月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1195 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
11月前
|
移动开发 Rust JavaScript
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1247 4
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
12月前
|
数据安全/隐私保护 开发者
鸿蒙应用开发从入门到实战(十一):ArkUI组件Text&TextInput
ArkUI提供了丰富的系统组件,用于制作鸿蒙原生应用APP的UI,本文主要讲解文本组件Text和TextInput的使用。
671 3
|
12月前
|
存储 缓存 5G
鸿蒙 HarmonyOS NEXT端云一体化开发-云存储篇
本文介绍用户登录后获取昵称、头像的方法,包括通过云端API和AppStorage两种方式,并实现上传头像至云存储及更新用户信息。同时解决图片缓存问题,添加上传进度提示,支持自动登录判断,提升用户体验。
471 1
|
11月前
|
移动开发 Android开发
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
498 0

热门文章

最新文章