注意:博主有个鸿蒙专栏,里面从上到下有关于鸿蒙next的教学文档,大家感兴趣可以学习下
如果大家觉得博主文章写的好的话,可以点下关注,博主会一直更新鸿蒙next相关知识
专栏地址: https://blog.csdn.net/qq_56760790/category_12794123.html
文章所属类目(HarmonyOS 语言-ArkTS)
目录
1. 渲染-条件渲染
1.1 基本介绍
1.2 if/else
1.3 visibility属性控制
1.4 多种条件控制
2. 渲染-循环渲染
2.1 ForEach:循环渲染
2.1.1 基本介绍
2.1.2 语法
2.1.3 代码示例
2.1.4 key的推荐建议
3. 下拉刷新+上拉加载
3.1 下拉刷新
3.2 上拉加载
1. 渲染-条件渲染
1.1 基本介绍
在ArkTS中 我们要根据某个状态来控制元素或者组件的显示隐藏 可以采用条件渲染
- if/else(创建销毁元素)
- visibility属性控制
1.2 if/else
- 支持if、else和else if语句。
- if、else if后跟随的条件语句可以使用状态变量或者常规变量(状态变量:值的改变可以实时渲染UI,常规变量:值的改变不会实时渲染UI)。
- 允许在容器组件内使用,通过条件渲染语句构建不同的子组件。
- 条件渲染语句在涉及到组件的父子关系时是“透明”的,当父组件和子组件之间存在一个或多个if语句时,必须遵守父组件关于子组件使用的规则。
- 每个分支内部的构建函数必须遵循构建函数的规则,并创建一个或多个组件。无法创建组件的空构建函数会产生语法错误。
- 某些容器组件限制子组件的类型或数量,将条件渲染语句用于这些组件内时,这些限制将同样应用于条件渲染语句内创建的组件。例如,Grid容器组件的子组件仅支持GridItem组件,在Grid内使用条件渲染语句时,条件渲染语句内仅允许使用GridItem组件。
注意:
当if、else if后跟随的状态判断中使用的状态变量值变化时,条件渲染语句会进行更新,更新步骤如下:
- 评估if和else if的状态判断条件,如果分支没有变化,无需执行以下步骤。如果分支有变化,则执行2、3步骤。
- 删除此前构建的所有子组件。
- 执行新分支的构造函数,将获取到的组件添加到if父容器中。如果缺少适用的else分支,则不构建任何内容。
@Entry
@Component
struct Demo1 {@State count: number = 0;build() {Column() {Text(`count=${this.count}`)if (this.count > 0) {Text(`count is positive`).fontColor(Color.Green)}Button('increase count').onClick(() => {this.count++;})Button('decrease count').onClick(() => {this.count--;})}}
}
显示隐藏
@Entry@Componentstruct Index {@State isShow:boolean=truebuild() {Column() {Button('显示/隐藏').width(100).height(30).onClick(()=>{if(this.isShow){this.isShow=false}else{this.isShow=true}})if(this.isShow){Text('我是东林').width(200).height(200).fontSize(40)}}.width('100%').height('100%')}}
1.3 visibility属性控制
visibility属性有以下三种:
1、Visible 显示
2、Hidden 隐藏
3、None 隐藏,但是不占位置
@Entry@Componentstruct Demo2 {@State isShow:boolean=truebuild() {Column() {Button('显示/隐藏').width(100).height(30).onClick(()=>{if(this.isShow){this.isShow=false}else{this.isShow=true}})Text('我是东林').width(200).height(200).fontSize(40).backgroundColor(Color.Green).visibility(this.isShow?Visibility.Visible:Visibility.Hidden)Text('小头').width(200).height(200).fontSize(40).backgroundColor(Color.Yellow)}.width('100%').height('100%')}}
1.4 多种条件控制
分析:
1.页面排版布局样式实现
2.下拉框的双向绑定
3.条件渲染
@Entry@ComponentV2struct Demo3 {@Local myVip: number = 0;@Local optionValue: string = '暂不开通'build() {Column({ space: 20 }) {Row() {Text('开通会员:')Select([{ value: '暂不开通' },{ value: 'VIP' },{ value: 'SVIP' }]).width('50%').selected($$this.myVip).value($$this.optionValue)// .onSelect((index, value) => {// this.myVip = index// this.optionValue = value// })}Row({ space: 20 }) {Image($r('app.media.img')).width(30).borderRadius(30)Text('西北吴彦祖')if (this.myVip === 0) {Text('VIP').VIPStyle(this.myVip).backgroundColor('#ccc')} else if (this.myVip === 1) {Text('VIP').VIPStyle(this.myVip).backgroundColor('#ffffb803')} else if (this.myVip === 2) {Text('SVIP').VIPStyle(this.myVip).backgroundColor('#ffb00909')}}.width('100%').justifyContent(FlexAlign.Center)}.width('100%').padding(20)}}@Extend(Text)function VIPStyle(type: number) {.padding({left: 12,right: 12,bottom: 4,top: 4}).fontColor('#fff').borderRadius(20).fontSize(12)}
2. 渲染-循环渲染
- ForEach-最常用的
- LazyForEach-懒加载渲染
2.1 ForEach:循环渲染
2.1.1 基本介绍
ForEach接口基于数组类型数据来进行循环渲染,需要与容器组件配合使用,且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。例如,ListItem组件要求ForEach的父容器组件必须为List组件。
2.1.2 语法
ForEach(// 数据源arr: Array,// 组件生成函数itemGenerator: (item: 单项, index?: number) => void,// 键值生成函数keyGenerator?: (item: 单项, index?: number): string => string
)
ForEach
接口基于数组类型数据来进行循环渲染,需要与容器组件配合使用。
2.1.3 代码示例
interface PayRecord {OrderName:stringOrderDate:DateOrderAmount:number
}
@Entry@ComponentV2struct ForEachDemo {PayRecordList: PayRecord[] = [{OrderName: '给老婆买口红',OrderDate: new Date('2024/05/11'),OrderAmount: 399.00},{OrderName: '给老婆买花',OrderDate: new Date('2024/05/12'),OrderAmount: 99.00},{OrderName: '给自己买手机',OrderDate: new Date('2024/05/13'),OrderAmount: 9999.00}]build() {Column() {// 标题Row() {Text('支付记录').layoutWeight(1).textAlign(TextAlign.Center).margin({left: 30})}.width('100%').padding(16).border({width: {bottom: 1},color: '#f4f5f6'})// 列表Column() {// 要循环的结构体Column({ space: 20 }) {Text('给老婆买了一朵花').fontWeight(FontWeight.Bold).width('100%')Row() {Text('¥43.00').fontColor(Color.Red)Text('2024/5/11')}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.width('100%').padding(20)}.width('100%')}.width('100%').height('100%')}}
页面中生成数据,用ForEach循环
ForEach(this.PayRecordList, (item: PayRecord) => {// 要循环的结构体Column({ space: 20 }) {Text(item.OrderName).fontWeight(FontWeight.Bold).width('100%')Row() {Text(`¥${item.OrderAmount}`).fontColor(Color.Red)Text(item.OrderDate.toLocaleDateString())}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.width('100%').padding(20)
})
interface PayRecord {OrderName:stringOrderDate:DateOrderAmount:number
}
@Entry
@ComponentV2
struct ForEachDemo2 {PayRecordList: PayRecord[] = [{OrderName: '给老婆买口红',OrderDate: new Date('2024/05/11'),OrderAmount: 399.00},{OrderName: '给老婆买花',OrderDate: new Date('2024/05/12'),OrderAmount: 99.00},{OrderName: '给自己买手机',OrderDate: new Date('2024/05/13'),OrderAmount: 9999.00}]build() {Column() {// 标题Row() {Text('支付记录').layoutWeight(1).textAlign(TextAlign.Center).margin({left: 30})}.width('100%').padding(16).border({width: {bottom: 1},color: '#f4f5f6'})// 列表Column() {ForEach(this.PayRecordList, (item: PayRecord) => {// 要循环的结构体Column({ space: 20 }) {Text(item.OrderName).fontWeight(FontWeight.Bold).width('100%')Row() {Text(`¥${item.OrderAmount}`).fontColor(Color.Red)Text(item.OrderDate.toLocaleDateString())}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.width('100%').padding(20)})}.width('100%')}.width('100%').height('100%')}
}
2.1.4 key的推荐建议
在ForEach循环渲染过程中,系统会为每个数组元素生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。
ForEach提供了一个名为keyGenerator的参数,这是一个函数,开发者可以通过它自定义键值的生成规则。如果开发者没有定义keyGenerator函数,则ArkUI框架会使用默认的键值生成函数,即(item: Object, index: number) => { return index + '__' + JSON.stringify(item); }。
ForEach的第三个属性是一个回调,它是生成唯一key的
不传的话会帮助我们生成独一无二的key => index_ + JSON.stringify(item)
鸿蒙更新的原理:循环的比较-比较你的key存在不,如果存在相同的key,则不更新
只改动了某一条数据,可能所有列表都会更新
ForEach的第三个参数 宁可不给 也不要瞎给
- 下面是key的使用案例
interface Person {id: numbername: stringage: number
}@Entry@ComponentV2struct ForEachDemo3 {@Local heroList: Person[] = [{ id: 1, name: '吕布', age: 18 },{ id: 2, name: '张飞', age: 20 },{ id: 3, name: '貂蝉', age: 21 }]build() {Column() {Button() {Text('在第1项后插入新项').fontSize(30)}.onClick(() => {this.heroList.splice(1, 0, {id: Math.random(),name: '吕蒙',age: 20});})ForEach(this.heroList, (item: Person) => {ChildItem({ item: item })})}.justifyContent(FlexAlign.Center).width('100%').height('100%').backgroundColor(0xF1F3F5)}}@ComponentV2struct ChildItem {@Param item: Person = {} as Person;aboutToAppear(): void {console.log('我被渲染了', this.item.name)}build() {Text(this.item.name + this.item.age).fontSize(30)}}
会发现使用index作为key,一旦中间项变化,后面的所有项,都需要重新渲染更新,影响性能
转而修改成以 id 作为 key,测试发现性能提升不少,只有对应项需要更新
interface Hero {id: numbername: stringage: number
}@Entry@ComponentV2struct ForEachDemo4 {@Local heroList: Hero[] = [{ id: 1, name: '吕布', age: 18 },{ id: 2, name: '张飞', age: 20 },{ id: 3, name: '貂蝉', age: 21 },]build() {Column() {Button() {Text('在第1项后插入新项').fontSize(30)}.onClick(() => {this.heroList.splice(1, 0, {id: Math.random(),name: '吕蒙',age: 20});})ForEach(this.heroList, (item: Hero) => {ChildItem({ item: item })}, (item: Hero) => item.id.toString())}.justifyContent(FlexAlign.Center).width('100%').height('100%').backgroundColor(0xF1F3F5)}}@ComponentV2struct ChildItem {@Param item: Hero = {} as Hero;aboutToAppear(): void {console.log('我被渲染了', this.item.name)}build() {Text(this.item.name + this.item.age).fontSize(30)}}
3. 下拉刷新+上拉加载
3.1 下拉刷新
Refresh组件支持下拉刷新,包裹List组件,下拉事件中更新列表
基础结构
@Entry
@ComponentV2
struct RefreshCase {@Local list: number[] = Array(20).fill(Date.now())build() {Column() {List() {ForEach(this.list, (item: number) => {ListItem() {Row() {Text(item.toString())}.width('100%').padding(20).border({width: {bottom: 1},color: Color.Gray,})}})}}.height('100%').width('100%')}
}
添加Refresh组件
@Entry
@ComponentV2
struct RefreshCase2 {@Local list: number[] = Array(20).fill(Date.now())@Local refreshing: boolean = false@BuilderrefreshContent() {Text('正在加载中...').width('100%').textAlign(TextAlign.Center)}build() {Column() {Refresh({ refreshing: $$this.refreshing, builder: this.refreshContent }) {List() {ForEach(this.list, (item: number) => {ListItem() {Row() {Text(item.toString())}.width('100%').padding(20).border({width: {bottom: 1},color: Color.Gray,})}})}}.onRefreshing(() => {setTimeout(() => {this.list = Array(20).fill(Date.now())this.refreshing = false}, 1000)})}.height('100%').width('100%')}
}
3.2 上拉加载
滚动至列表尾部(会立刻触发两次,滚动到底部+回弹一下)
import { promptAction } from '@kit.ArkUI'@Entry@ComponentV2struct RefreshCase3 {@Local list: number[] = Array(20).fill(Date.now())@Local refreshing: boolean = false@Local isLoading: boolean = false // 标记是否正在加载中scroller: Scroller = new Scroller()@BuilderrefreshContent() {Text('正在加载中...').width('100%').textAlign(TextAlign.Center)}build() {Column() {Refresh({refreshing: $$this.refreshing,builder: this.refreshContent}) {List({ scroller: this.scroller }) {ForEach(this.list, (item: number) => {ListItem() {Row() {Text(item.toString())}.width('100%').padding(20).border({width: {bottom: 1},color: Color.Gray,})}})if (this.isLoading) {ListItem() {Text('正在拼命加载中...').width('100%').textAlign(TextAlign.Center).height(80)}}}.onReachEnd(() => {if (!this.isLoading) {this.isLoading = true // 开始加载, 显示Loading动画this.scroller.scrollEdge(Edge.End)setTimeout(() => {this.list.push(...Array(10).fill(Date.now()))promptAction.showToast({message: '10条数据添加成功'})this.isLoading = false}, 2000)}})}.onRefreshing(() => {setTimeout(() => {this.list = Array(20).fill(Date.now())this.refreshing = false}, 1000)})}.height('100%').width('100%')}}