鸿蒙5:条件-循环-列表渲染

目录

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后跟随的状态判断中使用的状态变量值变化时,条件渲染语句会进行更新,更新步骤如下:

  1. 评估if和else if的状态判断条件,如果分支没有变化,无需执行以下步骤。如果分支有变化,则执行2、3步骤。
  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%')
    }
  }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值