
HarmonyOS样式处理 原创
1.起步鸿蒙
ArkTS以声明方式组合和扩展组件来描述应用程序的UI;
同时还提供了基本的属性、事件和子组件配置方法,帮助开发者实现应用交互逻辑。
(1)样式属性
· 属性方法以“.”链式调用的方式配置系统组件的样式和其他属性。
@Entry
@Component
struct Index {
build() {
Text('演示…')
.backgroundColor('red')
.fontSize(50)
.width('100%')
.height(100)
}
}
(2)枚举值
· 对于系统组件,ArkUI还为其属性预定义了一些枚举类型。
@Entry
@Component
struct Index {
build() {
Text('演示…')
.fontSize(50)
.width('100%')
.height(100)
.backgroundColor(Color.Blue)
.textAlign(TextAlign.Center)
.fontColor(Color.White)
}
}
样式相关属性通过链式函数的方式进行设置。
如果类型是枚举的,通过枚举传入对应的值。
注意:有的属性强烈建议使用枚举(大部分枚举值都是数字,但是数字无法体现代码含义)
有的组件如fontColor可以使用系统自带颜色枚举,也可以使用色值。
2.样式-单位vp和适配
知道 vp 单位,以及适配思想
(1) vp 是什么?virtual pixel
· 屏幕密度相关像素,根据屏幕像素密度转换为屏幕物理像素,当数值不带单位时,默认单位 vp;在实际宽度为1440物理像素的屏幕上,1vp 约等于 3px(物理像素)
在样式中,如果写px,那么px直接表示的是物理像素,也就是分辨率,那么手机分辨率密度各有不同,无法针对这种密度写一个固定值,所以vp会自动根据手机密度去进行适配,所以vp它提供了一种灵活的方式来适应不同屏幕密度的显示效果。
设计图按照1080设计- 换算成360写vp就可以了
上图的意思是,使用这个单位在不同屏幕物理分辨率的实际尺寸一致(A设备1英寸,B设备1英寸)。
(2)之前 vw 、rem 和 rpx 相对于屏幕宽度的单位,可以实现等比例适配,vp 可以吗?
@Entry
@Component
struct StyleCase {
@State message: string = 'Hello
World'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%').alignItems(2)
}.width('100%')
.height('100%')
.onAreaChange((_, newArea) => {
AlertDialog.show({ message: JSON.stringify(newArea) })
})
}
}
运行效果如图:
我们发现:不同的设备屏幕的宽度 `vp` 是不一致的,那怎么适配呢?
(3)根据官方的文档,采用:伸缩布局,网格系统,栅格系统进行布局适配。
伸缩 layoutWeight(flex: number) 占剩余空间多少份,可以理解成CSS的
我们可以使用layoutWeight属性,让右侧内容去占满剩余宽度。
build() {
Row() {
Text("左侧内容")
Text("右侧内容")
.textAlign(TextAlign.End)
.width('80%')
.height(60)
.backgroundColor('red')
.layoutWeight(1)
}.width('100%')
.height('100%')
}
运行效果如图:
内容等比例缩放-可以使用aspectRatio属性设置宽高比。
设置元素宽高比 aspectRatio(ratio: number)。
如我们如果希望一个元素始终占整个屏幕宽度的50%,且为一个正方形。
Column()
.width('50%')
.height('50%')
.backgroundColor('blue')
.aspectRatio(1)
测试代码如下:
@Entry
@Component
struct AspectRatioCase {
build() {
Text('left')
.width('50%')
// 宽高比例
.aspectRatio(1)
.backgroundColor('red')
}
}
运行效果
vp 是鸿蒙默认单位,和屏幕像素有关,最终表现视觉大小在任何设备一致
鸿蒙一般以伸缩 layoutWeight、网格、栅格进行布局适配,如要等比例缩放可以设置高宽比
3.Image和资源Resource
项目开发离不开图片-图片在页面中必须使用Image组件。
Image为图片组件,常用于在应用中显示图片。Image支持加载string、PixelMap和Resource类型的数据源,支持png、jpg、bmp、svg和gif类型的图片格式。
使用本地图片-拖一张图片放置到ets目录下-比如assets文件下
代码如下:
@Entry
@Component
struct IconCase {
build() {
Column(){
Image('/assets/startIcon.png')
.width(100)
.height(100)
}
}
}
使用Resource下的图片-media
代码如下:
@Entry
@Component
struct IconCase2 {
build() {
Column(){
Image($r('app.media.icon'))
.width(200)
.height(200)
}
}
}
使用Resource下的图片-rawfile
代码如下:
@Entry
@Component
struct IconCase3 {
build() {
Column(){
Image($rawfile('icon.png'))
.width(150)
.height(150)
}
}
}
使用网络图片
代码如下:
@Entry
@Component
struct IconCase4 {
build() {
Column(){
Image('https://gips0.baidu.com/it/u=838505001,1009740821&fm=3028&app=3028&f=PNG&fmt=auto&q=100&size=f254_80')
.width(150)
.height(150)
}
}
}
尤其注意:使用网络图片时,在preview中时,可以预览,但是在模拟器和真实项目中,必须申请网络权限
修改module.json5文件
"requestPermissions": [{
"name":"ohos.permission.INTERNET"
}],
2.1. 案例实操
接下来,我们手写一个知乎的评论
设计稿一般是1080px:(这里没有设计稿,提供了一些尺寸)。
· Nav
o 左侧返回按钮24vp高宽背景颜色#f5f5f5,图标12vp尺寸颜色#848484
o 标题18vp
· Comment
o 头像尺寸32vp高宽,右侧间距10vp
o 标题15vp,颜色默认
o 内容16vp,颜色#565656
o 底部12vp,颜色#c3c4c5
代码如下:
@Entry
@Component
struct ZhiHu {
build() {
Column () {
HmNavBar()
HmCommentItem()
}
}
}
@Component
struct HmNavBar {
build() {
Row(){
Row() {
Image($r("app.media.ic_public_back"))
.width(16)
.height(16)
.fillColor('#848484')
}
.justifyContent(FlexAlign.Center)
.width(24)
.aspectRatio(1)
.backgroundColor("#f5f5f5")
.borderRadius(12)
.margin({
left: 15
})
Text("评论回复")
.layoutWeight(1)
.textAlign(TextAlign.Center)
.fontSize(18)
.padding({
right: 39
})
}
.height(40)
.border({
color: '#f4f4f4',
width: {
bottom: 0.5
}
})
}
}
@Component
struct HmCommentItem {
build() {
Row() {
// 左侧头像
Image('https://p6-passport.byteacctimg.com/img/user-avatar/ab07005f9ec8119331b5f92c41c6fc92~90x90.awebp')
.width(32)
.aspectRatio(1)
.borderRadius(16)
// 评论区
Column({ space: 10 }) {
Text("我要写代码")
.fontWeight(FontWeight.Bold)
Text("一套代码工程,一次开发上架,多端按需部署,为用户呈现多设备统一且卓越的使用体验。")
.lineHeight(20)
.fontSize(16)
.fontColor('#565656')
// 底部内容
Row() {
Text("12-11 .ip属地深圳")
.fontSize(12)
.fontColor("#c3c4c5")
Row() {
Image($r("app.media.ic_public_heart"))
.width(12)
.aspectRatio(1)
.fillColor('#c3c4c5') // 填充颜色
.margin({
right: 5
})
Text("1020")
.fontSize(12)
.fontColor("#c3c4c5")
}
}.justifyContent(FlexAlign.SpaceBetween)
.width('100%')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({
left: 10
})
}.padding(15)
.alignItems(VerticalAlign.Top)
}
}
2.2. iconfont 中 svg 图片处理
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 1024 1024" version="1.1">
<title>Public/ic_public_todo</title>
<defs>
<path d="M747.4176 0 276.6848 474.077867 276.616533 473.975467
238.933333 511.8976 239.035733 511.965867 238.933333 512.068267 276.616533
550.058667 276.718933 549.956267 747.4176 1024 785.066667 986.043733 314.368
511.965867 785.066667 37.922133Z" id="_path-1" fill="#262626"/>
</defs>
<g id="_Public/ic_public_todo" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<mask id="_mask-2" fill="white">
<use xlink:href="#_path-1"/>
</mask>
<use id="_形状结合" fill="#000000" fill-rule="nonzero" xlink:href="#_path-1"/>
</g>
</svg>
只需要将 iconfont 里面图片的 svg 代码中的 path 字段中的 d 属性的值,复制到上面的 path 字段的 d 属性中的即可。
<svg t="1736264680746" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6249" width="40" height="40"><path d="M747.4176 0 276.6848 474.077867 276.616533 473.975467
238.933333 511.8976 239.035733 511.965867 238.933333 512.068267 276.616533
550.058667 276.718933 549.956267 747.4176 1024 785.066667 986.043733 314.368
511.965867 785.066667 37.922133Z" fill="#272636" p-id="6250"></path></svg>
4.样式-@Styles 复用
注意:Styles和Extend均只支持在当前文件下的全局或者组件内部定义,如果想要在其他文件导出一个公共样式,导出公共使用,ArtTS是不支持的,这种方式还是需要考虑组件复用。
在开发过程中会出现大量代码在进行重复样式设置,@Styles 可以帮我们进行样式复用。
通用属性通用事件:
(1)在Styles修饰的函数中能够点出来就是通用属性和事件。
(2)Styles修饰的函数不允许传参数。
当前 @Styles 仅支持通用属性 和通用事件。
(1)全局Styles不支持箭头函数语法。
(2)支持
全局定义和
组件内定义,同时存在组件内覆盖全局生效。
代码如下:
import promptAction from '@ohos.promptAction'
@Styles function textStyle () {
.width(100)
.height(50)
.backgroundColor(Color.Pink)
.borderRadius(25)
.onClick(() => {
promptAction.showToast({
message: "测试"
})
})
}
@Entry
@Component
struct StyleRepeatCase {
@Styles
textStyle () {
.width(200)
.height(50)
.backgroundColor(Color.Pink)
.borderRadius(25)
.onClick(() => {
promptAction.showToast({
message: "测试~~~"
})
})
}
build() {
Row() {
Column({ space: 15 }) {
Text("测试")
.textStyle()
.textAlign(TextAlign.Center)
Text("测试2")
.textStyle()
.textAlign(TextAlign.Center)
}
.width('100%')
}
.height('100%')
}
}
运行效果:
5.样式-@Extends 复用
假设我们就想针对 Text进行字体和样式的复用,此时可以使用Extend来修饰一个全局的方法。
· 使用 @Extend 装饰器修饰的函数只能是
全局
· 函数可以进行
传参,如果参数是状态变量,状态更新后会刷新UI
· 且参数可以是一个函数,实现复用事件且可处理不同逻辑
// 全局 原生组件 参数
// ↓ ↓ ↓
@Extend(Text) function textInputAll (callback?: () => void) {
.width(100)
.height(50)
.backgroundColor(Color.Pink)
.borderRadius(25)
.textAlign(TextAlign.Center)
.fontColor(Color.White)
.onClick(() => {
callback && callback()
})
}
需求:把 Text 改成按钮样式,且绑定 click 事件执行不同逻辑
代码如下:
@Entry
@Component
struct ExtendCase {
build() {
Row() {
Column({ space: 20 }) {
Text("测试2")
.text2Style()
Button("按钮")
.btnStyle()
TextInput()
.inputStyle()
}
.width('100%')
}
.height('100%')
}
}
@Extend(Text) function text2Style
() {
.width(120)
.height(60)
.borderRadius(30)
.backgroundColor(Color.Pink)
.textAlign(TextAlign.Center)
.fontSize(20)
}
@Extend(Button) function btnStyle () {
.width(120)
.height(60)
.borderRadius(30)
.backgroundColor(Color.Pink)
.fontSize(20)
}
@Extend(TextInput) function inputStyle
() {
.width(120)
.height(60)
.borderRadius(30)
.backgroundColor(Color.Pink)
.textAlign(TextAlign.Center)
.fontSize(20)
}
运行效果:
6.多态样式-stateStyles
@Styles和@Extend仅仅应用于静态页面的样式复用,stateStyles可以依据组件的内部状态的不同,快速设置不同样式。这就是我们本章要介绍的内容stateStyles(又称为:多态样式)。
ArkUI 提供以下四种状态:
· focused:获焦态
· normal:正常态
· pressed:按压态
· disabled:不可用态
准备一个扩展样式。
@Extend(Text) function textStyle() {
.width(100)
.height(40)
.textAlign(TextAlign.Center)
.borderRadius(20)
.backgroundColor(Color.Gray)
.fontColor(Color.White)
}
@Entry
@Component
struct Index {
build() {
Column(){
Text('按钮').textStyle()
}
.width('100%')
.height('100%')
}
}
运行效果:
我们想要在按压Text的时候给它一个扩大两倍的效果。
代码如下:
@Extend(Text)
function textStateStyle() {
.width(100)
.height(40)
.textAlign(TextAlign.Center)
.borderRadius(20)
.backgroundColor(Color.Gray)
.fontColor(Color.White)
}
@Entry
@Component
struct StateStylesCase {
@State message: string = 'Hello World'
@State textEnable: boolean = true
@Styles
pressStyle() {
.width(200)
.height(80)
.borderRadius(40)
.backgroundColor(Color.Pink)
}
@Styles
disableStyle() {
.backgroundColor(Color.Red)
}
@Styles
inputFocusStyle() {
.border({
color: Color.Red,
width: 1
})
}
@Styles
inputNormalStyle (){
.border({
color: Color.Red,
width: 0
})
}
build() {
Row() {
Column({ space: 20 }) {
Row() {
TextInput()
.height(40)
.stateStyles({
focused: this.inputFocusStyle,
normal: this.inputNormalStyle
})
}.padding({
left: 10,
right: 10
})
Row() {
TextInput()
.height(40)
.stateStyles({
focused: this.inputFocusStyle,
normal: this.inputNormalStyle
})
}.padding({
left: 10,
right: 10
})
Text("测试")
.textStateStyle()
.stateStyles({
pressed: this.pressStyle,
disabled: this.disableStyle
}).enabled(this.textEnable)
Button("文本设置禁用")
.onClick(() => {
this.textEnable = !this.textEnable
})
}
.width('100%')
}
.height('100%')
}
}
运行效果:
目前版本的编辑工具里不能设置通用属性之外的样式,比如fontColor 和TextAlign不可设置-会有异常。
禁用状态样式
build() {
Row() {
Column({ space: 15 }) {
Text("登录")
.textStyle()
.stateStyles({
pressed: this.textStylePressed,
disabled: {
.backgroundColor('#ccc')
}
}).enabled(!this.disable)
Button("文本禁用/开启")
.onClick(() => {
this.disable = !this.disable
})
}
.width('100%')
}
.height('100%')
}
获焦状态
(1)获焦状态最好使用TextInput来测试
(2)坑点- 要同时设置TextInput的normal和focus属性
TextInput()
.stateStyles({
normal: {
.border({
width: 0,
color: 'red'
})
},
focused: {
.border({
width: 1,
color: 'red'
})
}
})
TextInput()
.stateStyles({
focused: {
.border({
width: 1,
color: 'red'
})
},
normal: {
.border({
width: 0,
color: 'red'
})
},
})
总体代码如下:
@Extend(Text)
function textStateStyle2() {
.width(100)
.height(40)
.textAlign(TextAlign.Center)
.borderRadius(20)
.backgroundColor(Color.Gray)
.fontColor(Color.White)
}
@Entry
@Component
struct StateStylesCase2 {
@State message: string = 'Hello World'
@State textEnable: boolean = true
@Styles
pressStyle() {
.width(200)
.height(80)
.borderRadius(40)
.backgroundColor(Color.Pink)
}
@Styles
disableStyle() {
.backgroundColor(Color.Red)
}
@Styles
inputFocusStyle() {
.border({
color: Color.Red,
width: 1
})
}
@Styles
inputNormalStyle (){
.border({
color: Color.Red,
width: 0
})
}
build() {
Row() {
Column({ space: 20 }) {
Row() {
TextInput()
.height(40)
.stateStyles({
focused: this.inputFocusStyle,
normal: this.inputNormalStyle
})
}.padding({
left: 10,
right: 10
})
Row() {
TextInput()
.height(40)
.stateStyles({
focused: this.inputFocusStyle,
normal: this.inputNormalStyle
})
}.padding({
left: 10,
right: 10
})
Text("测试")
.textStateStyle2()
.stateStyles({
pressed: this.pressStyle,
disabled: this.disableStyle
}).enabled(this.textEnable)
Button("文本设置禁用")
.onClick(() => {
this.textEnable = !this.textEnable
})
}
.width('100%')
}
.height('100%')
}
}
运行效果:
