🎉 博客主页:【剑九_六千里-CSDN博客】【剑九_六千里-掘金社区】
🎨 上一篇文章:【HarmonyOS第三章:初识ArkTs/ArkUI,常用组件二】
🎠 系列专栏:【HarmonyOS系列】
💖 感谢大家点赞👍收藏⭐评论✍
引言:
ArkTS,作为ArkUI框架的一个重要组成部分,提供了一套强大且灵活的工具集,旨在帮助开发者构建高性能且美观的应用程序。无论是新手还是经验丰富的开发者,掌握ArkTS中的样式操作和渲染技术都是必不可少的技能。本文将深入探讨如何利用ArkTS中的样式设置、适配单位、样式复用、多态样式以及条件渲染和循环渲染等功能,帮助您更高效地构建美观、响应迅速的应用界面。让我们一同探索这些强大的工具和技术,开启您的ArkTS之旅吧!
文章目录
- 1. 样式操作
- 1.1. 样式语法
- 1.1.1. 样式属性
- 1.1.2. 枚举值
- 1.2. 像素单位
- 1.2.1. `"vp"` 是指 "虚拟像素"(virtual pixel)
- 1.2.2. 像素单位转换
- 1.2.3. 也可以采用伸缩布局(layoutWeight)、网格系统和栅格系统来进行布局适配。
- 1.3. 复用 @Styles
- 1.4. 复用 @Extend
- 1.5. 多态样式
- 2. 渲染页面
- 2.1. 条件渲染 if/else
- 2.2. 循环渲染 ForEach
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() {// 预览器宽度:360vpText(`屏幕宽度是${this.pageSize}vp`)// vp和px之间的转换比例是1:3Text(`1080px可以转换成${px2vp(1080)}vp`) // 将px转换成vpText(`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 = falsebuild() {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 = -1build() {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=truebuild() {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 = firstNamethis.nameContent = nameContent}
}@Entry
@Component
struct APage {private NameList:NameClass[] = [new NameClass("李",["李里","李芳","李月","李飞","李藏"]),new NameClass("刘",["刘备","刘彻","刘思雨"]),new NameClass("王",["王玉华","王岁","王喜文","王华"])]@BuilderHeaderTitle(title:string) {Text(title).fontSize(20).fontWeight(900).height(50).backgroundColor('#ccc').width('100%')}@BuilderdelButton() {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
生成键。