鸿蒙控制渲染常用的基础api以及结合新闻案例分析

256 阅读4分钟

LazyForEach && ForEach

鸿蒙系统中的 LazyForEach 和 ForEach 都是用于遍历集合的方法,但它们在实现和使用上有一些区别:

  1. 执行方式

    • ForEachForEach 方法会立即对集合中的每一项进行操作,也就是说,所有的元素都会在此方法被调用时立即被处理。这种方式适合处理较小的集合,因为它会一次性加载所有数据进行遍历。
    • LazyForEachLazyForEach 是一种惰性执行的方式,它不会立即处理所有元素,而是按需处理。当你实际请求下一个元素时才会进行处理。这对于处理大量数据或者需要按需加载数据的情况下非常有用。
  2. 性能

    • 因为 LazyForEach 是惰性处理,它能够在处理大数据集时节省内存和计算资源。只有在实际需要的时候才会执行相应的操作。
    • ForEach 由于是立即执行,对于小集合的操作是直接和快速的,但在处理大规模数据时可能会造成性能问题,特别是在内存使用上。
  3. 使用场景

    • ForEach 更适合用于小型的数据集和需要一次性处理所有数据的场景。
    • LazyForEach 则适合于大数据集或需要渐进式处理的场景,比如分页加载或流式处理数据。
ForEach 使用注意事项
  • 为满足键值的唯一性,对于对象数据类型,建议使用对象数据中的唯一id作为键值。
  • 尽量避免在最终的键值生成规则中包含数据项索引index,以防止出现渲染结果非预期渲染性能降低。如果业务确实需要使用index,例如列表需要通过index进行条件渲染,开发者需要接受ForEach在改变数据源后重新创建组件所带来的性能损耗。
  • 基本数据类型的数据项没有唯一ID属性。如果使用基本数据类型本身作为键值,必须确保数组项无重复。因此,对于数据源会发生变化的场景,建议将基本数据类型数组转化为具备唯一ID属性的对象数据类型数组,再使用ID属性作为键值生成规则。
  • 对于以上限制规则,index参数存在的意义为:index是开发者保证键值唯一性的最终手段;对数据项进行修改时,由于itemGenerator中的item参数是不可修改的,所以须用index索引值对数据源进行修改,进而触发UI重新渲染。
  • ForEach在下列容器组件 ListGridSwiper以及WaterFlow 内使用的时候,不要与LazyForEach 混用。 以List为例,同时包含ForEach、LazyForEach的情形是不推荐的。
LazyForEach 使用注意事项(数据懒加载的使用)
  • 容器组件内使用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列表的组件上,见使用规则
LazyForEach用法
// 数据源 IDataSource 类型 必须实现 IDataSource 接口
interface IDataSource {
    totalCount(): number; // 获得数据总数
    getData(index: number): Object; // 获取索引值对应的数据
    registerDataChangeListener(listener: DataChangeListener): void; // 注册数据改变的监听器
    unregisterDataChangeListener(listener: DataChangeListener): void; // 注销数据改变的监听器
}
// LazyForEach用法
List({ space: 3 }) {
  LazyForEach(this.data, (item: string) => {
    ListItem() {
      Row() {
        Text(item).fontSize(50)
          .onAppear(() => {
            console.info("appear:" + item)
          })
      }.margin({ left: 10, right: 10 })
    }
    .onClick(() => {
      // 点击追加子组件
      this.data.pushData(`Hello ${this.data.totalCount()}`);
    })
  }, (item: string) => item)
}.cachedCount(5)
ForEach用法
// ForEach 基础效果(拖拽排序官方demo前所未有的简单就实现了,纯前端写的话估计没这么方便)和基础用法
import { HMRouter, HMRouterMgr } from '@hadss/hmrouter'
// 路由装饰器要在@Builder下面
@HMRouter({ pageUrl: 'SendPage' })

@Entry
@Component
export struct Send {
  @State message: string = '我是第二个页面';
  @State list: Array<string> = ['vue', 'react', 'node'];
  @State arr: Array<string> = [];

  aboutToAppear(): void {
    for (let i = 0; i < 10; i++) {
      this.arr.push(i.toString())
    }
  }

  build() {
    RelativeContainer() {
      Column(){
        // ForEach 用法
        ForEach(this.list, (item: string) => {
          Text(item).fontColor(Color.Red)
        }, (item: string) => item)
        Column(){
          // List + ForEach 实现拖拽排序
          List() {
            ForEach(this.arr, (item: string) => {
              ListItem() {
                Text(item.toString())
                  .fontSize(16)
                  .textAlign(TextAlign.Center)
                  .size({height: 100, width: "100%"})
              }.margin(10)
              .borderRadius(10)
              .backgroundColor("#FFFFFFFF")
            }, (item: string) => item)
              .onMove((from:number, to:number) => {
                let tmp = this.arr.splice(from, 1);
                this.arr.splice(to, 0, tmp[0])
              })
          }
          .width('100%')
          .height('100%')
          .backgroundColor("#FFDCDCDC")
          Button('返回').width(100).height(100).backgroundColor('#eeeeee').onClick(()=>{
            HMRouterMgr.pop()
          })
        }
      }
    }
    .height('100%')
    .width('100%')
  }
}

if/else:条件渲染 单分支两分支 多分支都支持的

import { HMRouter, HMRouterMgr } from '@hadss/hmrouter'
// 路由装饰器要在@Builder下面
@HMRouter({ pageUrl: 'SendPage' })


@Entry
@Component
export struct Send {
  @State message: string = '我是第二个页面';
  @State count: number = 0;
  @State toggle: boolean = true;

  build() {
    RelativeContainer() {
      Column(){
        Text(`${this.count}`)
        // if 单个分支
        if(this.message){
          Text(`${this.message}`).fontColor(Color.Brown)
        }
        // if && else if && else
        if(this.count === 0){
          Text(`${this.count}`).fontColor(Color.Brown)
        } else if(this.count > 1){
          Text(`${this.count}`).fontColor(Color.Blue)
        } else {
          Text(`${this.count}`).fontColor(Color.Red)
        }
        // if && else
        if(this.toggle){
          Text(`${this.count}`).fontColor(Color.Brown)
        } else {
          Text(`${this.count}`).fontColor(Color.Red)
        }
        Button('increase count')
          .onClick(() => {
            this.count++;
          })
        Button(`toggle的 ${this.toggle} 效果`)
          .onClick(() => {
            this.toggle = !this.toggle;
          })
        Column(){
          Button('返回').width(100).height(100).backgroundColor('#eeeeee').onClick(()=>{
            HMRouterMgr.pop()
          })
        }
      }
    }
    .height('100%')
    .width('100%')
  }
}

新闻案例demo剖析学习

新闻案例 gitee仓库

体验流畅的首页信息流

介绍

本场景解决方案主要面向于新闻类页面开发人员,指导开发者从零开始构建一个新闻类的首页面,包含地址选择、tabs和tabContent切换的动态图标和流畅动效、下拉刷新上拉加载、首页feed流等常见功能,及功能的流畅体验。

效果预览

image.png

使用说明

  1. 获取地理位置的权限;
  2. 点击位置信息,跳转地址页,可修改当前位置信息;
  3. 点击顶部页签或者滑动切换页面,页签同步切换;
  4. 点击底部页签切换页面,同步切换页签,触发页签切换的动画效果;
  5. 下拉刷新页面信息;
  6. 上拉加载页面信息;
  7. 点击右下角按钮回弹至顶部。

工程目录 (代码组织和结构可以参考官方最佳实践的demo先上手再说吧)

├──entry/src/main/ets/
│  ├──common
│  │  └──lottie                    // 动画
│  ├──constants
│  │  ├──BreakpointConstants.ets   // 断点常量
│  │  ├──CommonConstants.ets       // 常用常量
│  │  └──HomeConstants.ets         // 主页常量
│  ├──entryability
│  │  └──EntryAbility.ets          // Ability的生命周期回调内容
│  ├──pages
│  │  ├──CitySearch.ets            // 城市查询
│  │  └──Index.ets                 // 首页
│  ├──util                  
│  │  ├──BreakpointType.ets        // 断点类型
│  │  └──ResourceUtil.ets          // 路由数据
│  ├──view                  
│  │  ├──CityView.ets              // 城市列表组件
│  │  ├──Home.ets                  // 主页组件
│  │  ├──HomeContent.ets           // tab内容组件
│  │  ├──HomeHeader.ets            // 主页头部组件
│  │  ├──NewsChannel.ets           // 新闻渠道组件
│  │  ├──PullToRefreshNews.ets     // 拉取刷新新闻组件
│  │  ├──SearchView.ets            // 搜索组件
│  │  └──TabBar.ets                // 标签栏组件
│  └──viewmodel                  
│     ├──CityDetailData.ets        // 城市详细数据
│     ├──NewsData.ets              // 新闻数据
│     ├──NewsDataSource.ets        // 新闻数据源
│     ├──NewsTypeModel.ets         // 新闻类型模型
│     └──NewsViewModel.ets         // 新闻视图模型
└──entry/src/main/resources        // 应用静态资源目录

依赖

本方案使用了三方库lottie(Lottie是一个适用于OpenHarmony的动画库,它可以解析Adobe After Effects软件通过Bodymovin插件导出的json格式的动画,并在移动设备上进行本地渲染。支持动画的交互性,通过添加触摸事件与TabBar相结合可实现动态图标效果)和pulltorefresh(使用第三方库pullToRefresh组件,将列表组件、绑定的数据对象和scroller对象包含进去,并添加上滑与下拉方法。支持lazyForEach的数据作为数据源,使用的List组件需要设置edgeEffect属性为(EdgeEffect.None))

## 安装命令
ohpm install @ohos/pulltorefresh  
ohpm install @ohos/lottie

相关权限

获取定位权限:ohos.permission.APPROXIMATELY_LOCATIONohos.permission.LOCATION【在src/main/module.json5中配置权限】 特别的上拉下拉接口如何是接口获取的则还需要加网络权限ohos.permission.INTERNET【网络相关的都需要该权限】

"requestPermissions": [
  {
    "name": "ohos.permission.APPROXIMATELY_LOCATION",
    "reason": "$string:approximately_location_desc",
    "usedScene": {
      "abilities": [
        "EntryAbility"
      ],
      "when": "always"
    }
  },
  {
    "name": "ohos.permission.LOCATION",
    "reason": "$string:location_desc",
    "usedScene": {
      "abilities": [
        "EntryAbility"
      ],
      "when": "always"
    }
  }
],

约束与限制

1.本示例仅支持标准系统上运行,支持设备:华为手机。

2.HarmonyOS系统:HarmonyOS NEXT Developer Beta1及以上。

3.DevEco Studio版本:DevEco Studio NEXT Developer Beta1及以上。

4.HarmonyOS SDK版本:HarmonyOS NEXT Developer Beta1 SDK及以上。

核心知识点

  • mock数据
  • Grid布局、Flex布局、Column、Row布局、Stack布局
  • 生命周期
  • 缓存 AppStorage 相关文档、@StorageLink
  • GPS资源 geoLocationManager相关文档
  • 状态管理
  • 组件(组合、封装)
  • @ohos.abilityAccessCtrl (程序访问控制管理) 申请用户安全权限
  • hilog 日志管理
  • pulltorefresh 上拉下拉 (三方库)、LazyForEachForEach pulltorefresh 三方库地址
  • lottie 动画库(三方库)lottie 三方库地址
  • 布局单位vp