HarmonyOS第四章:样式操作及渲染页面

🎉 博客主页:【剑九_六千里-CSDN博客】【剑九_六千里-掘金社区
🎨 上一篇文章:【HarmonyOS第三章:初识ArkTs/ArkUI,常用组件二
🎠 系列专栏:【HarmonyOS系列
💖 感谢大家点赞👍收藏⭐评论✍

在这里插入图片描述

在这里插入图片描述
引言
ArkTS,作为ArkUI框架的一个重要组成部分,提供了一套强大且灵活的工具集,旨在帮助开发者构建高性能且美观的应用程序。无论是新手还是经验丰富的开发者,掌握ArkTS中的样式操作和渲染技术都是必不可少的技能。本文将深入探讨如何利用ArkTS中的样式设置、适配单位、样式复用、多态样式以及条件渲染和循环渲染等功能,帮助您更高效地构建美观、响应迅速的应用界面。让我们一同探索这些强大的工具和技术,开启您的ArkTS之旅吧!

1. 样式操作

1.1. 样式语法

ArkTS采用声明式方法来组合和扩展组件,从而构建应用程序的用户界面。此外,它还提供了一系列基础属性、事件处理能力和子组件的配置选项,以支持开发者设计和实现应用程序的交互逻辑。

1.1.1. 样式属性

  • 属性方法可以通过点(.)进行链式调用来配置系统组件的样式和其他属性。
  • 为了提高代码的可读性,建议将每个属性方法分别写在单独的一行上。
@Entry
@Component
struct StylePage {
  build() {
    Row() {
      Text("测试链式调用")
        .width(100)
        .height(100)
        .backgroundColor("#f40")
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

测试:

在这里插入图片描述

1.1.2. 枚举值

ArkUI为系统组件的属性提供了预定义的枚举类型,以简化配置过程。

@Entry
@Component
struct StylePage {
  build() {
    Row() {
      Text("测试样式枚举值")
        .width(200)
        .height(100)
        .backgroundColor(Color.Gray)
        .textAlign(TextAlign.Center)
        .fontWeight(FontWeight.Bold)
        .fontColor(Color.Blue)
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

测试:

在这里插入图片描述

注意:

  • 样式相关的属性应该使用链式函数进行设置。
  • 当属性类型为枚举时,应该通过传入相应枚举值来设定。

1.2. 像素单位

Harmony为开发者提供4种像素单位,框架采用 vp 为基准数据单位。

名称描述
px屏幕物理像素单位。
vp屏幕密度相关像素,根据屏幕像素密度转换为屏幕物理像素,当数值不带单位时,默认单位vp。在实际宽度为1440物理像素的屏幕上,1vp约等于3px。
fp字体像素,与vp类似适用屏幕密度变化,随系统字体大小设置变化。
lpx视窗逻辑像素单位,lpx单位为实际屏幕宽度与逻辑宽度(通过designWidth配置)的比值,designWidth默认值为720。当designWidth为720时,在实际宽度为1440物理像素的屏幕上,1lpx为2px大小。

1.2.1. "vp" 是指 “虚拟像素”(virtual pixel)

在设置样式时,如果使用的是 px 作为单位,它直接对应于物理像素,即屏幕的分辨率。由于不同手机的屏幕分辨率密度各异,用 px 表示的值难以适应所有屏幕密度,因此使用 vp(虚拟像素)作为单位可以自动根据手机的屏幕密度进行适配。这样,vp 提供了一种灵活的方法来确保不同屏幕密度下的显示效果保持一致。

例如,设计图如果是按照1080px宽度设计的,那么在编写样式时,可以将这个尺寸换算成360vp来适配不同的屏幕。

1.2.2. 像素单位转换

其他单位与px单位互相转换的方法。

接口描述
vp2px(value : number) : number将vp单位的数值转换为以px为单位的数值。从API version 9开始,该接口支持在ArkTS卡片中使用。
px2vp(value : number) : number将px单位的数值转换为以vp为单位的数值。从API version 9开始,该接口支持在ArkTS卡片中使用。
fp2px(value : number) : number将fp单位的数值转换为以px为单位的数值。从API version 9开始,该接口支持在ArkTS卡片中使用。
px2fp(value : number) : number将px单位的数值转换为以fp为单位的数值。从API version 9开始,该接口支持在ArkTS卡片中使用。
lpx2px(value : number) : number将lpx单位的数值转换为以px为单位的数值。从API version 9开始,该接口支持在ArkTS卡片中使用。
px2lpx(value : number) : number将px单位的数值转换为以lpx为单位的数值。从API version 9开始,该接口支持在ArkTS卡片中使用。

示例:

@Entry
@Component
struct VPPage {
  @State pageSize: string = "";
  build() {
    Column() {
      // 预览器宽度:360vp
      Text(`屏幕宽度是${this.pageSize}vp`)
      // vp和px之间的转换比例是1:3
      Text(`1080px可以转换成${px2vp(1080)}vp`) // 将px转换成vp
      Text(`360vp可以转换成${vp2px(360)}px`) // 将vp转换成px
    }
    .width('100%')
    .height('100%')
    .onAreaChange((oldValue, newValue) => {
      // 视口发生变化时触发
      this.pageSize = newValue.width.toString();
    })
  }
}

测试:

在这里插入图片描述

1.2.3. 也可以采用伸缩布局(layoutWeight)、网格系统和栅格系统来进行布局适配。

在伸缩布局中,layoutWeight(flex: number)可以用来指定组件占据剩余空间的比例,类似于CSS中的 flex: 1 属性。这意味着组件可以根据指定的权重来分配剩余空间,以实现灵活的布局。

@Entry
@Component
struct LayoutWeight {
  build() {
    Row() {
      Text('left')
        .width(100)
        .height(100)
        .backgroundColor(Color.Pink)
      Text('right')
        .width(100)
        .height(100)
        .backgroundColor(Color.Red)
        .layoutWeight(1)
    }.width('100%')
    .height(100)
  }
}

在这里插入图片描述

等比例,设置元素宽高比 aspectRatio(ratio: number)

@Entry
@Component
struct AspectRatio {
  build() {
    Text('测试')
      .width(200)
      .backgroundColor(Color.Blue)
      .aspectRatio(2) // 宽高比
  }
}

在这里插入图片描述

1.3. 复用 @Styles

在开发过程中,我们经常需要在大量的代码中进行重复的样式设置。@Styles 可以帮助我们实现样式的复用,从而减少重复的代码。

  • 当前 @Styles 仅支持 通用属性 和 通用事件。
  • 它支持在全局范围和组件内部进行样式的定义,并且允许组件内的样式覆盖全局样式以实现个性化效果。

按照之前的写法,我们会写出如下的代码:

// 全局
@Entry
@Component
struct Index {
  build() {
    Row() {
      Column() {
        Text('1')
      }
        .width(100)
        .height(100)
        .backgroundColor(Color.Brown)
      Column() {
        Text('2')
      }
        .width(100)
        .height(100)
        .backgroundColor(Color.Pink)
      Column() {
        Text('3')
      }
        .width(100)
        .height(100)
        .backgroundColor(Color.Gray)
    }
    .height('100%')
    .width('100%')
  }
}

在这里插入图片描述

可以看到这其中有些样式代码是重复的,因此可以将上面的代码进行改造:


@Entry
@Component
struct Index {
  @Styles commonColumnStyle() {
    .width(100)
    .height(100)
    .onClick(() => {
      AlertDialog.show({
        message: '我被点击了~~~~',
        alignment: DialogAlignment.Top
      })
    })
  }
  build() {
    Row() {
      Column() {
        Text('1')
      }
        .commonColumnStyle()
        .backgroundColor(Color.Brown)
      Column() {
        Text('2')
      }
        .commonColumnStyle()
        .backgroundColor(Color.Pink)
      Column() {
        Text('3')
      }
        .commonColumnStyle()
        .backgroundColor(Color.Gray)
    }
    .height('100%')
    .width('100%')
  }
}

代码的复用性更强,逻辑更清晰。

在这里插入图片描述

1.4. 复用 @Extend

@Extend 装饰器用于扩展原生组件的样式,通过传递参数来提供更灵活的样式复用功能。

  • 使用 @Extend 修饰的函数必须是全局函数。
  • 这些函数可以接受参数,如果参数是状态变量,当状态更新时,UI将被刷新。
  • 参数还可以是函数,从而实现事件的复用,并能够处理不同的逻辑。
// 全局  原生组件                     参数
//  ↓     ↓                          ↓ 
@Extend(Text) function functionName(w: number) { 
  .width(w)
}

需求:点击 click 事件执行不同逻辑:

import promptAction from '@ohos.promptAction'

@Extend(Text) function commonTextStyle(size:number, color:string, width: number, height: number, cb:()=>void){
  .fontSize(size)
  .fontColor(color)
  .width(width)
  .height(height)
  .backgroundColor(Color.Red)
  .onClick(()=>{
    cb()
  })
}

@Entry
@Component
struct Index {
  build() {
    Column(){
      Text("元素1")
        .commonTextStyle(30, "#fff",100, 100, ()=>{
          promptAction.showToast({ message: 'hello' })
        })

      Text("元素2")
        .commonTextStyle(40, "#ccc", 200, 200, ()=>{
          promptAction.showToast({ message: 'world' })
        })

    }.width("100%")
    .height("100%")
  }
}

在这里插入图片描述

1.5. 多态样式

stateStyles() 是一个属性方法,它可以根据组件内部状态的不同,快速设置不同的样式。这种功能类似于CSS中的伪类选择器,但在语法上有所不同。在ArkUI中,您可以使用stateStyles 来定义四种不同的状态样式,分别是:

  • focused:获得焦点的状态。
  • normal:正常状态。
  • pressed:按压状态。
  • disabled:不可用状态。

通过这些状态样式的定义,您可以根据组件的不同状态来动态调整样式,以实现更灵活的界面效果。

@Entry
@Component
struct Index {
  @State disabled: boolean = false

  build() {
    Column() {
      Text('toggle disabled:' + this.disabled)
        .height(100)
        .fontSize(30)
        .backgroundColor(Color.Grey)
        .onClick(()=>{
          this.disabled = !this.disabled
        })

      Button("普通按钮")
        .stateStyles({
          // 正常状态
          normal:{
            .backgroundColor(Color.Blue)
          },
          // 按压状态
          pressed:{
            .backgroundColor(Color.Red)
          }
        })

      TextInput({placeholder:"请输入用户名"})
        .stateStyles({
          // 正常状态
          normal:{
            .backgroundColor(Color.Pink)
          },
          // 获得焦点的状态
          focused:{
            .backgroundColor(Color.Red)
          }
        })

      Button("禁用按钮")
        .enabled(this.disabled)
        .stateStyles({
          // 正常状态
          normal:{
            .backgroundColor(Color.Red)
          },
          // 禁用状态
          disabled:{
            .backgroundColor(Color.Black)
          }
        })
    }
  }
}

在这里插入图片描述

注意:

  • 在实际使用中,最常见的情况是使用 normalpressed 样式结合来实现按压效果。
  • 使用 enabled(true|false) 可以控制组件的启用或禁用状态,而 focusable(true|false) 可以控制组件是否具备获取焦点的能力。
  • 请注意,页面初始化时,默认情况下第一个具备获取焦点能力的组件会自动获得焦点

2. 渲染页面

2.1. 条件渲染 if/else

条件渲染是一种根据应用的不同状态使用 ifelseelse if 来渲染相应状态下的UI内容的方法。

  • 条件渲染是根据状态数据进行判断,以展示不同的UI
  • 在条件渲染中,组件会根据条件的变化而销毁和创建,因此组件的状态不会被保留。

使用 if else 实现 下拉列表选择 效果:

interface ISelectData {
  value: string
}
@Entry
@Component
struct Index {
  @State SelectData: ISelectData[] = [{ value: "霍建华" },{ value: "周深" },{ value: "杨丽萍" }]
  @State selected: number = -1
  build() {
    Column() {
      Select(this.SelectData)
        .selected(0)
        .value("请选择")
        .onSelect((index) => {
          this.selected = index
        })
      if(this.selected === 0) {
        Text("演员")
      } else if(this.selected === 1) {
        Text("歌手")
      } else if(this.selected === 2) {
        Text("舞蹈家")
      } else {
        Text("普通人")
      }
    }
      .height("100%")
      .width("100%")
  }
}

测试:

在这里插入图片描述

控制元素高宽:

Text("1111")
        .width(this.isOn ? 100 : 0)
        .height(this.isOn ? 100 : 0)
        .borderRadius(8)

控制visibility属性- Hidden(占空间)和 None(不占空间)两种隐藏属性:

@Entry
@Component
struct APage {
  @State flag:boolean=true
  build() {
    Column() {
      Text("1111")
        .visibility(this.flag?Visibility.Visible:Visibility.Hidden)
        .borderRadius(8)
      Text("222")
    }
 }
}

2.2. 循环渲染 ForEach

ForEach 接口用于在数组类型的数据上进行循环渲染,并需要与容器组件一起使用以展示循环生成的内容。
语法:

ForEach(
  // 数据源
  arr: Array,
  // 组件生成函数
  itemGenerator: (item: Array, index?: number) => void,
  // 键值生成函数
  keyGenerator?: (item: Array, index?: number): string => string
)

应用1:

interface IArrObj {
  id:number,
  name:string
}
@Entry
@Component
struct Index {
  @State arrObj: IArrObj[] = [
    { id:1, name: "lili" },
    { id:2, name: "Tom" },
    { id:3, name: "Janny" }
  ]
  build() {
    Column() {
      // ForEach(this.arrObj,(item)=>{
      //    Text(`${new Date().getTime()}---${item.name}`)
      // },(item,index)=>index+JSON.stringify(item))

      //复用性更强
      ForEach(this.arrObj,(item: IArrObj) => {
        Text(`${new Date().getTime()}---${item.name}`)
      },(item: IArrObj) => JSON.stringify(item))

      Button("尾部新增一项").onClick(()=>{
        this.arrObj.push({id:this.arrObj.length,name:"丽萍"})
      })

      Button("调换位置").onClick(() => {
        this.arrObj=[this.arrObj[2],this.arrObj[0],this.arrObj[1]]
      })
    }.width('100%')
    .height('100%')
  }
}

测试:

在这里插入图片描述

应用2:

class NameClass {
  firstName:string = ""
  nameContent:string[] = []
  constructor(firstName: string, nameContent: string[]) {
    this.firstName = firstName
    this.nameContent = nameContent
  }
}

@Entry
@Component
struct APage {
  private NameList:NameClass[] = [
    new NameClass("李",["李里","李芳","李月","李飞","李藏"]),
    new NameClass("刘",["刘备","刘彻","刘思雨"]),
    new NameClass("王",["王玉华","王岁","王喜文","王华"])
  ]
  @Builder
  HeaderTitle(title:string) {
    Text(title)
      .fontSize(20)
      .fontWeight(900)
      .height(50)
      .backgroundColor('#ccc')
      .width('100%')
  }

  @Builder
  delButton() {
    Button("删除")
  }

  build() {
    Column(){
      List({ space:10 }) {
        ForEach(this.NameList,(item: NameClass) => {
          ListItemGroup({header:this.HeaderTitle(item.firstName),space:20}) {
            ForEach(item.nameContent,(itemCon: string) => {
              ListItem() {
                Text(itemCon)
              }.width('100%').height(50)
              .swipeAction({end:this.delButton()})
            })
          }.width("100%")
          .divider({ strokeWidth: 3, color:"#ccc" })
        },(item: NameClass) => JSON.stringify(item))
      }.width("100%")
      .height("100%")
      .alignListItem(ListItemAlign.Center)
      .listDirection(Axis.Vertical)
      .scrollBar(BarState.Auto)
    }
    .width("100%")
    .height("100%")
  }
}

测试:

在这里插入图片描述

有关 keyGenerator 键生成函数的一些建议如下:

  • 必须提供 keyGenerator 函数,不能省略。
  • 尽量避免在最终生成的键中包含索引(index)。
  • 当处理对象数组时,建议使用对象中的唯一标识符(如 id)作为键。
  • 如果处理基本数据类型的数组,建议先将其转换为具有唯一标识符的对象,然后再使用 keyGenerator 生成键。
  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

剑九_六千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值