🎉 博客主页:【剑九_六千里-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)
}
})
}
}
}
注意:
- 在实际使用中,最常见的情况是使用
normal
和pressed
样式结合来实现按压效果。 - 使用
enabled(true|false)
可以控制组件的启用或禁用状态,而focusable(true|false)
可以控制组件是否具备获取焦点的能力。 - 请注意,页面初始化时,默认情况下第一个具备获取焦点能力的组件会自动获得焦点。
2. 渲染页面
2.1. 条件渲染 if/else
条件渲染是一种根据应用的不同状态使用 if
、else
和 else 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
生成键。