LazyForEach && ForEach
鸿蒙系统中的 LazyForEach 和 ForEach 都是用于遍历集合的方法,但它们在实现和使用上有一些区别:
-
执行方式:
- ForEach:
ForEach方法会立即对集合中的每一项进行操作,也就是说,所有的元素都会在此方法被调用时立即被处理。这种方式适合处理较小的集合,因为它会一次性加载所有数据进行遍历。 - LazyForEach:
LazyForEach是一种惰性执行的方式,它不会立即处理所有元素,而是按需处理。当你实际请求下一个元素时才会进行处理。这对于处理大量数据或者需要按需加载数据的情况下非常有用。
- ForEach:
-
性能:
- 因为
LazyForEach是惰性处理,它能够在处理大数据集时节省内存和计算资源。只有在实际需要的时候才会执行相应的操作。 ForEach由于是立即执行,对于小集合的操作是直接和快速的,但在处理大规模数据时可能会造成性能问题,特别是在内存使用上。
- 因为
-
使用场景:
ForEach更适合用于小型的数据集和需要一次性处理所有数据的场景。LazyForEach则适合于大数据集或需要渐进式处理的场景,比如分页加载或流式处理数据。
ForEach 使用注意事项
- 为满足键值的唯一性,对于对象数据类型,建议使用对象数据中的唯一id作为键值。
- 尽量避免在最终的键值生成规则中包含数据项索引index,以防止出现渲染结果非预期和渲染性能降低。如果业务确实需要使用index,例如列表需要通过index进行条件渲染,开发者需要接受ForEach在改变数据源后重新创建组件所带来的性能损耗。
- 基本数据类型的数据项没有唯一ID属性。如果使用基本数据类型本身作为键值,必须确保数组项无重复。因此,对于数据源会发生变化的场景,建议将基本数据类型数组转化为具备唯一ID属性的对象数据类型数组,再使用ID属性作为键值生成规则。
- 对于以上限制规则,index参数存在的意义为:index是开发者保证键值唯一性的最终手段;对数据项进行修改时,由于itemGenerator中的item参数是不可修改的,所以须用index索引值对数据源进行修改,进而触发UI重新渲染。
- ForEach在下列容器组件 List、Grid、Swiper以及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剖析学习
体验流畅的首页信息流
介绍
本场景解决方案主要面向于新闻类页面开发人员,指导开发者从零开始构建一个新闻类的首页面,包含地址选择、tabs和tabContent切换的动态图标和流畅动效、下拉刷新上拉加载、首页feed流等常见功能,及功能的流畅体验。
效果预览
使用说明
- 获取地理位置的权限;
- 点击位置信息,跳转地址页,可修改当前位置信息;
- 点击顶部页签或者滑动切换页面,页签同步切换;
- 点击底部页签切换页面,同步切换页签,触发页签切换的动画效果;
- 下拉刷新页面信息;
- 上拉加载页面信息;
- 点击右下角按钮回弹至顶部。
工程目录 (代码组织和结构可以参考官方最佳实践的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_LOCATION和ohos.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 上拉下拉 (三方库)、
LazyForEach、ForEachpulltorefresh 三方库地址 - lottie 动画库(三方库)lottie 三方库地址
- 布局单位vp