目录
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
@Component
struct Index {
@State isShow:boolean=true
build() {
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
@Component
struct Demo2 {
@State isShow:boolean=true
build() {
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
@ComponentV2
struct 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:string
OrderDate:Date
OrderAmount:number
}
@Entry
@ComponentV2
struct 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:string
OrderDate:Date
OrderAmount: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: number
name: string
age: number
}
@Entry
@ComponentV2
struct 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)
}
}
@ComponentV2
struct 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: number
name: string
age: number
}
@Entry
@ComponentV2
struct 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)
}
}
@ComponentV2
struct 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
@Builder
refreshContent() {
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
@ComponentV2
struct RefreshCase3 {
@Local list: number[] = Array(20).fill(Date.now())
@Local refreshing: boolean = false
@Local isLoading: boolean = false // 标记是否正在加载中
scroller: Scroller = new Scroller()
@Builder
refreshContent() {
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%')
}
}