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;
}
目录
相关文章
|
4月前
|
容器
HarmonyOS NEXT仓颉开发语言实战案例:外卖App
仓颉语言实战分享,教你如何用仓颉开发外卖App界面。内容包括页面布局、导航栏自定义、搜索框实现、列表模块构建等,附完整代码示例。轻松掌握Scroll、List等组件使用技巧,提升HarmonyOS应用开发能力。
|
3月前
|
移动开发 前端开发 JavaScript
鸿蒙NEXT时代你所不知道的全平台跨端框架:CMP、Kuikly、Lynx、uni-app x等
本篇基于当前各大活跃的跨端框架的现状,对比当前它们的情况和未来的可能,帮助你在选择框架时更好理解它们的特点和差异。
312 0
|
4月前
|
安全 API 开发工具
【HarmonyOS NEXT】一键扫码功能
这些Kit为我们应用开发提升了极大地效率。很多简单的功能,如果不需要太深的定制化需求,直接调用kit提供的API就可以实现,在android或者ios上需要很多代码才能实现的功能效果。
119 0
HarmonyOS NEXT仓颉开发语言实战案例:电影App
周末好!本文分享使用仓颉语言重构ArkTS实现的电影App案例,对比两者在UI布局、组件写法及语法差异。内容包括页面结构、列表分组、分类切换与电影展示等。通过代码演示仓颉在HarmonyOS开发中的应用。##仓颉##ArkTS##HarmonyOS开发
|
4月前
|
容器
HarmonyOS NEXT仓颉开发语言实战案例:健身App
本期分享一个健身App首页的布局实现,顶部采用Stack容器实现重叠背景与偏移效果,列表部分使用List结合Scroll实现可滚动内容。代码结构清晰,适合学习HarmonyOS布局技巧。
HarmonyOS NEXT仓颉开发语言实战案例:小而美的旅行App
本文分享了一个旅行App首页的设计与实现,使用List容器搭配Row、Column布局完成个人信息、功能列表及推荐模块的排版,详细展示了HarmonyOS下的界面构建技巧。
|
18天前
|
存储 缓存 5G
鸿蒙 HarmonyOS NEXT端云一体化开发-云存储篇
本文介绍用户登录后获取昵称、头像的方法,包括通过云端API和AppStorage两种方式,并实现上传头像至云存储及更新用户信息。同时解决图片缓存问题,添加上传进度提示,支持自动登录判断,提升用户体验。
90 0
|
18天前
|
存储 负载均衡 数据库
鸿蒙 HarmonyOS NEXT端云一体化开发-云函数篇
本文介绍基于华为AGC的端云一体化开发流程,涵盖项目创建、云函数开通、应用配置及DevEco集成。重点讲解云函数的编写、部署、调用与传参,并涉及环境变量设置、负载均衡、重试机制与熔断策略等高阶特性,助力开发者高效构建稳定云端服务。
178 0
鸿蒙 HarmonyOS NEXT端云一体化开发-云函数篇
|
18天前
|
存储 JSON 数据建模
鸿蒙 HarmonyOS NEXT端云一体化开发-云数据库篇
云数据库采用存储区、对象类型、对象三级结构,支持灵活的数据建模与权限管理,可通过AGC平台或本地项目初始化,实现数据的增删改查及端侧高效调用。
50 0
|
18天前
|
存储 开发者 容器
鸿蒙 HarmonyOS NEXT星河版APP应用开发-ArkTS面向对象及组件化UI开发使用实例
本文介绍了ArkTS语言中的Class类、泛型、接口、模块化、自定义组件及状态管理等核心概念,并结合代码示例讲解了对象属性、构造方法、继承、静态成员、访问修饰符等内容,同时涵盖了路由管理、生命周期和Stage模型等应用开发关键知识点。
150 0
鸿蒙 HarmonyOS NEXT星河版APP应用开发-ArkTS面向对象及组件化UI开发使用实例