一百天挑战学会HarmanyOS——界面的渲染

565 阅读6分钟

大家好我是牛牛,一名软件开发从业者,无意中接触到了鸿蒙移动端开发,对鸿蒙操作系统产生了极大的兴趣,作者将从无到有开发出一款鸿蒙原生APP。每天写一篇关于鸿蒙开发的技术文章。欢迎大家踊跃订阅➕关注,文章中有什么不妥之处可以在评论区中指出。

注意:最好是有开发经验的伙伴来阅读系列文章。零基础的同学可以先去了解一下TypeScript从最基本的开发语言进行学起

前言

上一章节《一百天挑战学会HarmanyOS——图片资源的访问》中介绍了ArkUI 中使用Image组件对图片资源的访问,如果没有查看到这一章节内容的同学可以先去了解并且实操一下上一章节的内容。有什么疑问的地方大家可以在评论区留言。

本章介绍

本章主要介绍 ArkUI 中界面的条件渲染循环渲染,在本章中介绍的内容在实际开发过程当中会高频的使用,所以同学们要牢记本章的内容。本章内容如果同学们都做过 Vue 之类的前端开发可能会感觉轻松一点,更好的理解一点。下面就让我们开始今天的讲解吧!

页面顺序渲染

什么是顺序渲染?

在前期的开发当中,我们在设计UI 界面时,页面的元素都是固定下来的要去显示什么,开发时按照UI 元素的位置顺序进行摆放,在 ArkUI 页面渲染的过程当中就会按照摆放顺序进行渲染。

示例

image-20240707120611043

代码:

@Entry
@Component
struct SortCasePage {
  build() {
    // 竖向布局容器
    Column() {
      // 文本组件
      Text('元素 1')
      Text('元素 2')
      Text('元素 3')
      Text('元素 4')
      Text('元素 5')
      Text('元素 6')
    }
    // 设置主轴居中
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}

上述代码中的 Text 组件是在 Column 竖向容器中依次向下排列的,所以编译的结果就会像上图那样,会按照排列的顺序进行展示。这也就是我们开发当中用的最多的布局方式,页面会按照组件的摆放顺序进行渲染。

页面条件渲染 if

什么是条件渲染?

在开发 ArkUI页面时,因为业务需求可能根据某个数据的状态从而渲染不同的页面元素,我们需要一个特定条件去改变页面,这就是条件渲染。

条件渲染的语法:

  • 使用条件判断
if(condition param) {
  
} else {
  
}

condition param即 条件,当条件成立才会进入到 if 所包裹的代码块中。还有其他两种形态如下:

if(condition param) {
  
} else if (condition param){
  
} else {

}
if(condition param) {
  
} else if (condition param){
  
} else if (condition param){

}

代码示例:

@Entry
@Component
struct SortCasePage {
  // 定义一个 flag 默认为 false
  @State
  flag:boolean = true;
  build() {
    Column() {
      Text('元素 1')
      Text('元素 2')
      Text('元素 3')
      Text('元素 4')
      // 如果为 true 显示元素 5
      if(this.flag){
        Text('元素 5')
      } else {
        // 否则显示元素5-5
        Text('元素 5-5')
      }
    }.justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}

image-20240707131629662

注意:在页面的条件渲染中不支持 switch语法。

  • 使用visibility 属性方法控制。

代码示例:

@Entry
@Component
struct SortCasePage {
  build() {
    Column() {
      // 使用visibility控制组件显示与隐藏
      Text('元素 6').visibility(Visibility.Hidden)
    }.justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}

image-20240707131329299

⚠️注意:ifvisibility都会控制元素是否渲染,但是它们两个的的区别在于,使用 if 语法时条件以外的元素不会被渲染到页面上,类似于 Vue 开发中使用 v-if 不满足条件的元素不会渲染到 dom 节点上,当使用visibilityVisibility.Hidden 的参数进行控制时,页面元素只是在视觉上被隐藏,其实元素的框架还保留在页面上。

当使用 if 我们在if 之外再添加一个 5-3 的元素,此时屏幕应该依次渲染不会被分割开。

@Entry
@Component
struct SortCasePage {
  @State
  flag:boolean = true;
  build() {
    Column() {
      Text('元素 1')
      Text('元素 2')
      Text('元素 3')
      Text('元素 4')
      if(this.flag){
        Text('元素 5-5')
      } else {
        Text('元素 5')
      }
      // 在此处添加 5-3 的元素
      Text('元素 5-3')
      
    }.justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}

image-20240707132518418

当使用visibility进行控制时,我们让元素 6 隐藏,在后面再添加一个元素 7,此时画面的效果。

@Entry
@Component
struct SortCasePage {
  @State
  flag:boolean = true;
  build() {
    Column() {
      Text('元素 1')
      Text('元素 2')
      Text('元素 3')
      Text('元素 4')
      if(this.flag){
        Text('元素 5-5')
      } else {
        Text('元素 5')
      }
      Text('元素 5-3')
      //  使用Visibility.Hidden 隐藏元素 6
      Text('元素 6').visibility(Visibility.Hidden)
      // 添加元素 7
      Text('元素 7')
    }.justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}

image-20240707132758664

很明显元素 6的位置只是内容被隐藏了,但是元素框架还存在页面上。我们打开调试工具看一下。

image-20240707132934461

如果想实现与 if 同样的效果,visibility提供了另一个枚举:Visibility.None当使用这个枚举参数时,页面上就不会渲染对应的元素。

image-20240707133243160

下面让我做一个小案例实践一下条件渲染:

案例

需求:根据页面选择器中的 VIP 类型去渲染不同 VIP 所对应的图标与文字。

效果:

select

代码:

@Entry
@Component
struct IFCasePage {
  @State
  selectStatus: number = 0
  @State
  optionValue: string = "未开通"

  build() {
    Column() {
      Row({ space: 20 }) {
        Text('会员类型:')
        // 使用 Select 组件
        Select([{ value: '未开通' }, { value: 'VIP' }, { value: 'SVIP' }])
          .width(140)
          // 使用双向绑定绑定选中的值
          .value($$this.optionValue)
          // 使用双向绑定绑定选中值的下标
          .selected($$this.selectStatus)
      }.padding(15)

      Row({space:20}) {
        Image($r('app.media.ic_public_weixin')).width(40)
          .aspectRatio(1)
        Text('关惠民')
        // 使用条件判断,判断选择时的下标。进行对应组件的渲染。
        if(this.selectStatus === 0) {
          // 选中的值进行文字的渲染
          Text(this.optionValue).VipText()
            .backgroundColor("#ffbcbcbc")
        } else if (this.selectStatus === 1) {
          Text(this.optionValue).VipText()
            .backgroundColor("#fff86d6d")
        }else if (this.selectStatus === 2) {
          Text(this.optionValue).VipText()
            .backgroundColor("#fffacb3f")
        }
      }.margin({
        top:200
      })

    }
    .alignItems(HorizontalAlign.Center)
    .height('100%')
    .width('100%')
  }
}
// 使用样式的复用进行共同颜色的渲染
@Extend(Text)
function VipText(){
  .width(55)
  .padding(4)
  .fontSize(12)
  .borderRadius(25)
  .textAlign(TextAlign.Center)
  .fontColor("#fff")
  .fontStyle(FontStyle.Italic)
}

页面循环渲染 ForEach

什么是循环渲染?

页面中批量的渲染某一个组件或者封装好的组件,不用重复去编写同样的代码可以批量创建出同样的代码结构。就是页面的循环渲染。在 ArkUI 中我们使用 ForEach 组件进行页面的循环渲染。

  • 语法
ForEach([],(item,index)=>{},(item,index)=>{})
  • 参数说明

    • []被循环的数据,是一个数组类型。
    • (item,index)=>{} itemGenerator 被循环的数据类型与具体数据。
    • (item,index)=>{} keyGenerator 生成每条数据的 唯一key,并返回。底层实现 index_+JSON.stringify(item)
  • 在代码中使用

@Entry
@Component
struct ForEachCasePage {
  build() {
    Column() {
       ForEach([1,2,3],(item:string,index:number)=>{
         Text("测试数据"+ index).padding(5)
       })
    }
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}

image-20240707140513247

上述代码中ForEach 组件中定义了一个长度为 3 的数组,进行 Text 组件的批量渲染,并且把循环的下标拼接到文本当中。经过编译页面中渲染了三个Text组件。

案例

需求:

我们之前文章Scroll 组件做过页面滚动的案例,当时我们的页面数据是重复的组件代码顺次排列的,从而达到了效果,那么现在我想优化那个示例,进行一个微信消息列表的实现。

我们之前使用的方式:

image-20240707141329158

我们现在需要使用ForEach 组件进行页面的循环渲染。

效果图:

weicaht

代码:

@Entry
@Component
struct ListCasePage {

  /**
   *  消息数据
   */
  @State
  list: MessageType[] = [{
    image:"https://fastly.picsum.photos/id/504/200/200.jpg?hmac=uNktbiKQMUD0MuwgQUxt7R2zjHBGFxyUSG3prhX0FWM",
    name: "微信记账本",
    msg: "记账日报",
    time: new Date(),
    isNat: false,
    titleType: false
     },
    {
      image:"https://fastly.picsum.photos/id/1063/200/200.jpg?hmac=MY2ChBFr2WzXJRx0fJyztE7fXaMohAdg1Gh1U7yop1k",
      name: "HarmanyOS开发者",
      msg: "今天鸿蒙发布了!!!!",
      time: new Date(),
      isNat: false,
      titleType: false
    },
    {
      image:"https://fastly.picsum.photos/id/166/200/200.jpg?hmac=lghN9aMZHsvaZQVmJW3_fCu5ArnsnX8kJwM87m9K9dY",
      name: "微信运动",
      msg: "记账日报",
      time: new Date(),
      isNat: false,
      titleType: false
    },
    {
      image:"https://fastly.picsum.photos/id/504/200/200.jpg?hmac=uNktbiKQMUD0MuwgQUxt7R2zjHBGFxyUSG3prhX0FWM",
      name: "微信记账本",
      msg: "记账日报",
      time: new Date(),
      isNat: false,
      titleType: false
    },
    {
      image:"https://fastly.picsum.photos/id/1063/200/200.jpg?hmac=MY2ChBFr2WzXJRx0fJyztE7fXaMohAdg1Gh1U7yop1k",
      name: "HarmanyOS开发者",
      msg: "今天鸿蒙发布了!!!!",
      time: new Date(),
      isNat: false,
      titleType: false
    },
    {
      image:"https://fastly.picsum.photos/id/166/200/200.jpg?hmac=lghN9aMZHsvaZQVmJW3_fCu5ArnsnX8kJwM87m9K9dY",
      name: "微信运动",
      msg: "记账日报",
      time: new Date(),
      isNat: false,
      titleType: false
    },
    {
      image:"https://fastly.picsum.photos/id/504/200/200.jpg?hmac=uNktbiKQMUD0MuwgQUxt7R2zjHBGFxyUSG3prhX0FWM",
      name: "微信记账本",
      msg: "记账日报",
      time: new Date(),
      isNat: false,
      titleType: false
    },
    {
      image:"https://fastly.picsum.photos/id/1063/200/200.jpg?hmac=MY2ChBFr2WzXJRx0fJyztE7fXaMohAdg1Gh1U7yop1k",
      name: "HarmanyOS开发者",
      msg: "今天鸿蒙发布了!!!!",
      time: new Date(),
      isNat: false,
      titleType: false
    },
    {
      image:"https://fastly.picsum.photos/id/166/200/200.jpg?hmac=lghN9aMZHsvaZQVmJW3_fCu5ArnsnX8kJwM87m9K9dY",
      name: "微信运动",
      msg: "记账日报",
      time: new Date(),
      isNat: false,
      titleType: false
    },
    {
      image:"https://fastly.picsum.photos/id/504/200/200.jpg?hmac=uNktbiKQMUD0MuwgQUxt7R2zjHBGFxyUSG3prhX0FWM",
      name: "微信记账本",
      msg: "记账日报",
      time: new Date(),
      isNat: false,
      titleType: false
    },
    {
      image:"https://fastly.picsum.photos/id/1063/200/200.jpg?hmac=MY2ChBFr2WzXJRx0fJyztE7fXaMohAdg1Gh1U7yop1k",
      name: "HarmanyOS开发者",
      msg: "今天鸿蒙发布了!!!!",
      time: new Date(),
      isNat: false,
      titleType: false
    },
    {
      image:"https://fastly.picsum.photos/id/166/200/200.jpg?hmac=lghN9aMZHsvaZQVmJW3_fCu5ArnsnX8kJwM87m9K9dY",
      name: "微信运动",
      msg: "记账日报",
      time: new Date(),
      isNat: false,
      titleType: false
    },
    {
      image:"https://fastly.picsum.photos/id/504/200/200.jpg?hmac=uNktbiKQMUD0MuwgQUxt7R2zjHBGFxyUSG3prhX0FWM",
      name: "微信记账本",
      msg: "记账日报",
      time: new Date(),
      isNat: false,
      titleType: false
    },
    {
      image:"https://fastly.picsum.photos/id/1063/200/200.jpg?hmac=MY2ChBFr2WzXJRx0fJyztE7fXaMohAdg1Gh1U7yop1k",
      name: "HarmanyOS开发者",
      msg: "今天鸿蒙发布了!!!!",
      time: new Date(),
      isNat: false,
      titleType: false
    },
    {
      image:"https://fastly.picsum.photos/id/166/200/200.jpg?hmac=lghN9aMZHsvaZQVmJW3_fCu5ArnsnX8kJwM87m9K9dY",
      name: "微信运动",
      msg: "记账日报",
      time: new Date(),
      isNat: false,
      titleType: false
    }]

  //  构建右滑显示的页面
  @Builder
  swipeInner(){
    Flex(){
      Row(){
        Text("标为未读").fontColor(Color.White).textAlign(TextAlign.Center).width('100%').fontSize(14)
      }.height('100%').backgroundColor("#1285EE")
      Row(){
        Text("不显示").fontColor(Color.White).textAlign(TextAlign.Center).width('100%').fontSize(14)
      }.height('100%').backgroundColor("#FA9D3B")
      Row(){
        Text("删除").fontColor(Color.White).textAlign(TextAlign.Center).width('100%').fontSize(14)
      }.height('100%').backgroundColor("#FA5151")

    }.width('70%')
     .height('100%')
  }

  build() {
    Column() {
      // 使用相同高度对状态栏沉浸式
      Row()
      .width('100%')
      .backgroundColor("#EEEFEF")
      .height(45)
      // header
      Row() {
        Blank().width(20)
        Text('微信').fontSize(14).layoutWeight(1)
          .textAlign(TextAlign.Center)
          .width('100%')
        Row() {
          Image($r('app.media.ic_public_add_user')).width(20).fillColor("#141414")
        }
      }
      .padding(10)
      .backgroundColor("#EEEFEF")
      .height(45)
      // 使用 List 替代我们之前用的 Scroll
      List(){
        // 使用 循环渲染我们的数据
        ForEach(this.list,(messageItem:MessageType)=>{
          ListItem(){
            Row() {
              Row(){
                Image(messageItem.image)
                  .width(48)
                  .borderRadius(5)
              }.padding(5)

              Column({space:10}) {
                Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
                  Text(messageItem.name).layoutWeight(1).width('100%').fontSize(15).titleColor(messageItem.titleType)
                  Text(messageItem.time.toLocaleTimeString())
                    .fontColor("#CACACA")
                    .fontSize(12)
                }.padding({
                  top:5
                })

                Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
                  Text(messageItem.msg)
                    .fontColor("#CACACA")
                    .fontSize(12)
                    .layoutWeight(1)
                    .width('100%')
                  Image($r("app.media.ic_public_notnat")).width(15).aspectRatio(1)
                }.padding({
                  bottom:10
                })
              }.padding({
                left:5,
                right:5

              })
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)
            }
            .alignItems(VerticalAlign.Center)
            .padding({
              left:5,
              bottom:5,
              right:5,
              top:5
            })
            .width('100%')
          }.swipeAction({
            end: this.swipeInner()
          })
        })
      }.layoutWeight(1).divider({strokeWidth:1,color:"#F6F6F6",startMargin:65})

      Row(){
        Flex({direction:FlexDirection.Row,justifyContent:FlexAlign.SpaceAround}){
          Column({space:2}){
            Image($r('app.media.ic_public_msg')).width(20).aspectRatio(1)
            Text('微信').fontSize(8)
          }
          Column({space:2}){
            Image($r('app.media.ic_public_user')).width(20).aspectRatio(1)
            Text('通讯录').fontSize(8)
          }
          Column({space:2}){
            Image($r('app.media.ic_public_faxian')).width(20).aspectRatio(1)
            Text('发现').fontSize(8)
          }
          Column({space:2}){
            Image($r('app.media.ic_public_my')).width(20).aspectRatio(1)
            Text('微信').fontSize(8)
          }
        }
      }
      .width('100%')
      .backgroundColor("#EEEFEF")
      .height(45)
      Row(){

      }
      .width('100%')
      .backgroundColor("#EEEFEF")
      .height(30)
    }
    .height('100%')
    .width('100%')
  }
}

/**
 *  消息结构
 */
interface MessageType {
  // 头像
  image: string,

  // 名称
  name: string,

  // 消息
  msg: string,

  // 时间
  time: Date,

  // 是否通知
  isNat: boolean,

  titleType?: boolean
}
// 判断我们的标题类型设置不同的颜色
@Extend(Text)
function titleColor(titleType?:boolean){
 .fontColor(titleType?"#111111":"#5577A6")
}

上述代码中是使用了新的组件 List,新的装饰器@builder,这些内容将在下一期文章总进行详细的讲解,有兴趣的同学也可以自行研究一下。今天这期就到这里啦!文章中的代码已经上传到 项目代码git 仓库,欢迎大家的关注➕点赞➕分享