鸿蒙原生ArkTS布局方式之@Styles公共样式布局深度解析(API 24
项目演示



目录
- 引言:为什么需要公共样式复用
- @Styles 基础概念与设计哲学
- @Styles 语法详解
- 全局 @Styles 定义与使用
- 组件内 @Styles 定义与使用
- @Styles 限制条件深度解析
- 样式叠加与优先级机制
- 完整实战示例:@Styles 公共样式布局应用
- @Styles 与 @Extend 的对比与选型
- @Styles 与 AttributeModifier 的协作
- 多态样式 stateStyles 联用技巧
- 常见踩坑与排查方法
- 性能优化建议
- 最佳实践总结
1. 引言:为什么需要公共样式复用
1.1 开发痛点分析
在传统的 UI 开发中,开发者经常面临以下问题:
问题一:样式代码重复
// 传统写法:每个组件单独设置样式
Column() {
Text('按钮1')
.width(100)
.height(40)
.backgroundColor('#007DFF')
.borderRadius(20)
Text('按钮2')
.width(100)
.height(40)
.backgroundColor('#007DFF')
.borderRadius(20)
Text('按钮3')
.width(100)
.height(40)
.backgroundColor('#007DFF')
.borderRadius(20)
}
上述代码中,三个按钮的样式完全相同,但需要重复编写三次。当按钮数量增加到数十个时,代码冗余问题将变得非常严重。
问题二:维护成本高
假设产品需求变更,需要将按钮的圆角从 20vp 改为 24vp,背景色从 #007DFF 改为 #005AFF。在传统写法下,开发者需要找到所有相关的按钮组件,逐一修改样式属性,不仅效率低下,还容易遗漏。
问题三:视觉一致性难以保障
在多人协作开发中,不同开发者对"标准按钮样式"的理解可能存在差异。有的开发者可能设置圆角为 16vp,有的设置为 20vp,导致应用界面风格不统一,影响用户体验。
1.2 @Styles 的解决方案
@Styles 装饰器正是为了解决上述问题而设计的。它允许开发者将重复的样式属性抽取为一个可复用的方法,从而实现:
| 目标 | 实现方式 | 收益 |
|---|---|---|
| 代码复用 | 将公共样式封装为 @Styles 方法 | 减少代码量,提高开发效率 |
| 统一规范 | 集中管理样式定义 | 确保视觉一致性 |
| 易于维护 | 修改一处定义,全局生效 | 降低维护成本,减少出错风险 |
| 提升性能 | 减少样式重复计算 | 优化渲染性能 |
2. @Styles 基础概念与设计哲学
2.1 @Styles 是什么
@Styles 是 ArkTS 提供的一种样式复用机制,通过装饰器将多条样式属性封装为一个可调用的方法。使用时只需在组件上链式调用该方法,即可快速应用预定义的样式组合。
2.2 设计哲学
原则一:关注点分离
将样式定义与组件使用分离,使代码结构更加清晰。开发者可以专注于业务逻辑,同时保持样式的统一性。
原则二:作用域隔离
@Styles 支持两种作用域:
- 全局作用域:定义在组件外,可在整个文件中复用
- 组件内作用域:定义在组件内,仅在当前组件内可用
这种设计避免了样式污染,同时提供了灵活的复用粒度。
原则三:渐进增强
多个 @Styles 可以叠加调用,后调用的样式会覆盖前面的相同属性。这种机制允许开发者在基础样式上进行定制,实现样式的渐进增强。
2.3 API 版本演进
@Styles 装饰器自 API Version 9 开始支持,后续版本不断增强:
| API 版本 | 新增特性 |
|---|---|
| API 9 | 基础功能支持,支持组件内和全局定义 |
| API 9 | 支持 ArkTS 卡片 |
| API 11 | 支持元服务 |
| API 24 | 增强性能优化,完善类型检查 |
3. @Styles 语法详解
3.1 基本语法结构
@Styles 装饰器的基本语法如下:
// 全局 @Styles
@Styles
function styleName() {
.property1(value1)
.property2(value2)
// ... 更多样式属性
}
// 组件内 @Styles
@Entry
@Component
struct ComponentName {
@Styles
styleName() {
.property1(value1)
.property2(value2)
// ... 更多样式属性
}
build() {
// 使用样式
Text('示例')
.styleName()
}
}
3.2 关键语法要点
要点一:函数声明方式
- 全局 @Styles:必须使用
function关键字声明 - 组件内 @Styles:不能使用
function关键字
// 正确:全局 @Styles 使用 function 关键字
@Styles
function globalStyle() {
.width(100)
}
// 正确:组件内 @Styles 不使用 function 关键字
@Entry
@Component
struct Index {
@Styles
componentStyle() {
.width(100)
}
}
// 错误:全局 @Styles 不能省略 function 关键字
@Styles
globalStyle() { // 编译错误
.width(100)
}
要点二:链式调用
@Styles 方法内部使用链式调用语法,每个样式属性以 . 开头:
@Styles
function cardStyle() {
.width('90%') // 宽度
.padding(16) // 内边距
.borderRadius(12) // 圆角
.backgroundColor('#FFFFFF') // 背景色
}
要点三:调用方式
在组件上通过链式调用应用 @Styles:
Text('示例文本')
.cardStyle() // 应用样式
.fontSize(16) // 额外设置组件特有属性
4. 全局 @Styles 定义与使用
4.1 定义全局 @Styles
全局 @Styles 定义在所有组件外部,使用 function 关键字声明:
/**
* 全局卡片基础样式
* 适用场景:所有需要卡片效果的容器组件
*/
@Styles
function globalCardStyle() {
.width('90%')
.padding({ top: 16, right: 16, bottom: 16, left: 16 })
.borderRadius(12)
.backgroundColor('#FFFFFF')
.shadow({ radius: 8, color: '#0000001A', offsetY: 4 })
}
/**
* 全局按钮样式
* 适用场景:所有标准按钮
*/
@Styles
function globalButtonStyle() {
.height(48)
.borderRadius(24)
.backgroundColor('#007DFF')
}
/**
* 全局警告按钮样式
* 适用场景:警告类型按钮,叠加在 globalButtonStyle 上使用
*/
@Styles
function globalWarningButtonStyle() {
.backgroundColor('#FF6B35')
}
4.2 使用全局 @Styles
全局 @Styles 可以在文件内的任何组件中使用:
@Entry
@Component
struct Index {
build() {
Column({ space: 20 }) {
// 使用全局卡片样式
Column() {
Text('卡片内容')
}
.globalCardStyle()
// 使用全局按钮样式
Column() {
Text('普通按钮')
}
.globalButtonStyle()
.width('90%')
// 使用全局按钮样式 + 警告样式(叠加)
Column() {
Text('警告按钮')
}
.globalButtonStyle()
.globalWarningButtonStyle()
.width('90%')
}
}
}
4.3 全局 @Styles 的特点
| 特点 | 说明 |
|---|---|
| 作用域 | 当前文件内全局可用 |
| 声明位置 | 所有组件外部 |
| 声明方式 | 必须使用 function 关键字 |
| 访问组件状态 | 不支持访问组件的 @State、@Prop 等状态变量 |
| 事件处理 | 支持添加通用事件(如 onClick),但无法访问组件状态 |
| 优先级 | 低于组件内 @Styles |
5. 组件内 @Styles 定义与使用
5.1 定义组件内 @Styles
组件内 @Styles 定义在组件内部,不使用 function 关键字:
@Entry
@Component
struct Index {
@State count: number = 0
@State isExpanded: boolean = false
/**
* 计数器卡片样式
* 可以访问组件的状态变量
*/
@Styles
counterCardStyle() {
.backgroundColor('#F0F5FF')
.border({ width: 2, color: '#007DFF', style: BorderStyle.Solid })
}
/**
* 动态高度样式
* 根据状态变量动态调整样式
*/
@Styles
dynamicHeightStyle() {
.height(this.isExpanded ? 200 : 100)
}
build() {
Column() {
// 使用组件内样式
Column() {
Text(`计数: ${this.count}`)
}
.counterCardStyle()
// 使用动态样式
Column() {
Text('可展开区域')
}
.dynamicHeightStyle()
}
}
}
5.2 使用组件内 @Styles
组件内 @Styles 只能在当前组件的 build 方法中使用:
@Entry
@Component
struct Index {
@State message: string = 'Hello'
@Styles
customStyle() {
.width('80%')
.padding(12)
.backgroundColor('#E8F5E9')
}
build() {
Column() {
// 正确:在当前组件内使用
Text(this.message)
.customStyle()
}
}
}
// 错误:组件内 @Styles 不能在其他组件中使用
@Component
struct OtherComponent {
build() {
Column() {
Text('其他组件')
.customStyle() // 编译错误:customStyle 未定义
}
}
}
5.3 组件内 @Styles 的特点
| 特点 | 说明 |
|---|---|
| 作用域 | 仅当前组件内可用 |
| 声明位置 | 组件内部,build 方法之前 |
| 声明方式 | 不使用 function 关键字 |
| 访问组件状态 | 可以访问组件的 @State、@Prop、@Link 等状态变量 |
| 事件处理 | 支持添加通用事件,且可以在事件回调中修改组件状态 |
| 优先级 | 高于全局 @Styles |
5.4 组件内 @Styles 访问状态变量
组件内 @Styles 的一个重要特性是可以访问组件的状态变量,从而实现动态样式:
@Entry
@Component
struct DynamicStyleExample {
@State heightValue: number = 50
@State isHighlight: boolean = false
@Styles
dynamicCardStyle() {
.height(this.heightValue)
.backgroundColor(this.isHighlight ? '#FFEB3B' : '#FFFFFF')
.onClick(() => {
this.heightValue += 20
this.isHighlight = !this.isHighlight
})
}
build() {
Column() {
Text('点击改变样式')
.dynamicCardStyle()
.width('80%')
}
}
}
上述示例中,dynamicCardStyle 访问了组件的 heightValue 和 isHighlight 状态变量,并在点击事件中修改了这些变量,从而实现了样式的动态变化。
6. @Styles 限制条件深度解析
6.1 仅支持通用属性
核心限制:@Styles 方法内只能包含通用属性和通用事件,不能包含组件特有属性。
通用属性列表(部分)
| 属性类别 | 示例 |
|---|---|
| 尺寸属性 | width、height、minWidth、minHeight、maxWidth、maxHeight |
| 内边距 | padding、paddingTop、paddingRight、paddingBottom、paddingLeft |
| 外边距 | margin、marginTop、marginRight、marginBottom、marginLeft |
| 背景 | backgroundColor、backgroundImage、backgroundSize |
| 边框 | border、borderWidth、borderColor、borderStyle、borderRadius |
| 阴影 | shadow |
| 透明度 | opacity |
| 对齐 | align(部分组件) |
组件特有属性(不能放入 @Styles)
| 属性类别 | 示例 | 所属组件 |
|---|---|---|
| 字体属性 | fontSize、fontColor、fontWeight、fontFamily |
Text |
| 文本属性 | textAlign、maxLines、lineHeight |
Text |
| 输入属性 | placeholder、inputType、maxLength |
TextInput |
| 图片属性 | objectFit、interpolation |
Image |
| 布局属性 | justifyContent、alignItems、layoutWeight |
Column、Row |
错误示例
// 错误:fontSize 是 Text 组件特有属性,不能放入 @Styles
@Styles
function textStyle() {
.fontSize(16) // 编译错误
.fontColor('#333') // 编译错误
}
// 错误:justifyContent 是容器布局属性,不能放入 @Styles
@Styles
function flexCenterStyle() {
.justifyContent(FlexAlign.Center) // 编译错误
.alignItems(HorizontalAlign.Center) // 编译错误
}
// 正确:只包含通用属性
@Styles
function cardStyle() {
.width('90%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
6.2 不支持参数传递
核心限制:@Styles 方法不支持传入参数,所有样式值必须在方法内硬编码或通过组件状态变量获取。
错误示例
// 错误:@Styles 不支持参数
@Styles
function customWidthStyle(widthValue: number) {
.width(widthValue) // 编译错误
}
// 错误:同样不支持参数
@Styles
function coloredBox(color: string) {
.backgroundColor(color) // 编译错误
}
正确解决方案
方案一:定义多个 @Styles 方法
// 定义不同宽度的样式
@Styles
function widthSmall() {
.width(100)
}
@Styles
function widthMedium() {
.width(200)
}
@Styles
function widthLarge() {
.width(300)
}
方案二:使用组件内 @Styles 访问状态变量
@Entry
@Component
struct DynamicWidthExample {
@State boxWidth: number = 150
@Styles
dynamicWidthStyle() {
.width(this.boxWidth)
.height(100)
.backgroundColor('#007DFF')
}
build() {
Column() {
Text('动态宽度')
.dynamicWidthStyle()
Button('增加宽度')
.onClick(() => {
this.boxWidth += 50
})
}
}
}
方案三:使用 @Extend(支持参数传递)
// @Extend 支持参数传递
@Extend(Column)
function customWidth(widthValue: number) {
.width(widthValue)
.height(100)
}
@Entry
@Component
struct ExtendExample {
build() {
Column() {
Text('宽度 100')
}
.customWidth(100)
Column() {
Text('宽度 200')
}
.customWidth(200)
}
}
6.3 不支持逻辑组件
核心限制:@Styles 方法内不能使用逻辑组件(如 if、for、LazyForEach)。
错误示例
// 错误:@Styles 内不能使用 if 逻辑
@Styles
function conditionalStyle() {
if (true) {
.backgroundColor('#FF0000') // 编译错误
} else {
.backgroundColor('#00FF00') // 编译错误
}
}
// 错误:@Styles 内不能使用三元表达式
@Styles
function ternaryStyle() {
.backgroundColor(true ? '#FF0000' : '#00FF00') // 编译错误
}
正确解决方案
将逻辑判断移到组件使用处:
@Styles
function redBackground() {
.backgroundColor('#FF0000')
}
@Styles
function greenBackground() {
.backgroundColor('#00FF00')
}
@Entry
@Component
struct ConditionalStyleExample {
@State isError: boolean = true
build() {
Column() {
Text('条件样式')
.width(200)
.height(100)
// 在组件使用处进行条件判断
.apply(() => {
if (this.isError) {
return redBackground
} else {
return greenBackground
}
})
}
}
}
或者直接在组件上使用条件属性:
@Entry
@Component
struct ConditionalStyleExample {
@State isError: boolean = true
build() {
Column() {
Text('条件样式')
.width(200)
.height(100)
.backgroundColor(this.isError ? '#FF0000' : '#00FF00')
}
}
}
6.4 不支持 export
核心限制:@Styles 方法只能在当前文件内使用,不支持通过 export 导出到其他文件。
错误示例
// styles.ets 文件
@Styles
export function globalCardStyle() { // 编译错误:@Styles 不支持 export
.width('90%')
.backgroundColor('#FFFFFF')
}
// index.ets 文件
import { globalCardStyle } from './styles' // 编译错误:无法导入
正确解决方案
方案一:使用 @Extend(支持 export)
// styles.ets 文件
@Extend(Column)
export function cardStyle() {
.width('90%')
.padding(16)
.backgroundColor('#FFFFFF')
}
// index.ets 文件
import { cardStyle } from './styles'
@Entry
@Component
struct Index {
build() {
Column() {
Text('卡片内容')
}
.cardStyle()
}
}
方案二:使用 AttributeModifier(支持 export)
// styles.ets 文件
export class CardModifier extends AttributeModifier<ColumnAttribute> {
applyNormalAttribute(instance: ColumnAttribute): void {
instance.width('90%')
instance.padding(16)
instance.backgroundColor('#FFFFFF')
}
}
// index.ets 文件
import { CardModifier } from './styles'
@Entry
@Component
struct Index {
build() {
Column() {
Text('卡片内容')
}
.applyModifier(new CardModifier())
}
}
6.5 文件级作用域
核心限制:@Styles 的作用域仅限于当前文件,即使是全局 @Styles 也不能跨文件使用。
示例说明
// file1.ets
@Styles
function commonStyle() {
.width(100)
.height(50)
}
@Entry
@Component
struct Page1 {
build() {
Column() {
Text('Page1')
.commonStyle() // 正确:同一文件内可用
}
}
}
// file2.ets
@Component
struct Page2 {
build() {
Column() {
Text('Page2')
.commonStyle() // 编译错误:commonStyle 未定义
}
}
}
7. 样式叠加与优先级机制
7.1 样式叠加规则
多个 @Styles 可以叠加调用,后调用的样式会覆盖前面的相同属性:
@Styles
function baseStyle() {
.width(200)
.height(100)
.backgroundColor('#FFFFFF')
.borderRadius(8)
}
@Styles
function accentStyle() {
.backgroundColor('#007DFF') // 覆盖背景色
.borderRadius(16) // 覆盖圆角
}
@Entry
@Component
struct StyleOverlayExample {
build() {
Column() {
Text('叠加效果')
}
.baseStyle() // 应用基础样式
.accentStyle() // 叠加强调样式,覆盖背景色和圆角
// 最终样式:width=200, height=100, backgroundColor='#007DFF', borderRadius=16
}
}
7.2 优先级顺序
样式的优先级从低到高为:
- 全局 @Styles:定义在组件外的样式
- 组件内 @Styles:定义在组件内的样式
- 组件直接设置的属性:在组件上直接链式调用的属性
@Styles
function globalStyle() {
.width(100)
.height(50)
.backgroundColor('#FF0000')
}
@Entry
@Component
struct PriorityExample {
@Styles
componentStyle() {
.width(150) // 覆盖全局样式的 width
.backgroundColor('#00FF00') // 覆盖全局样式的 backgroundColor
}
build() {
Column() {
Text('优先级示例')
}
.globalStyle() // 优先级最低
.componentStyle() // 优先级中等,覆盖全局样式
.width(200) // 优先级最高,覆盖所有样式
// 最终样式:width=200, height=50, backgroundColor='#00FF00'
}
}
7.3 叠加示例
示例一:基础样式 + 主题样式
@Styles
function baseCard() {
.width('90%')
.padding(16)
.borderRadius(12)
}
@Styles
function primaryTheme() {
.backgroundColor('#E3F2FD')
.border({ width: 1, color: '#1976D2', style: BorderStyle.Solid })
}
@Styles
function successTheme() {
.backgroundColor('#E8F5E9')
.border({ width: 1, color: '#388E3C', style: BorderStyle.Solid })
}
@Entry
@Component
struct ThemeOverlayExample {
build() {
Column({ space: 16 }) {
// 基础卡片 + 主主题
Column() {
Text('主主题卡片')
}
.baseCard()
.primaryTheme()
// 基础卡片 + 成功主题
Column() {
Text('成功主题卡片')
}
.baseCard()
.successTheme()
}
}
}
示例二:多层叠加
@Styles
function level1() {
.width(100)
.height(100)
.backgroundColor('#FFFFFF')
}
@Styles
function level2() {
.height(150) // 覆盖 height
.borderRadius(8)
}
@Styles
function level3() {
.width(200) // 覆盖 width
.backgroundColor('#007DFF') // 覆盖 backgroundColor
}
@Entry
@Component
struct MultiLevelOverlay {
build() {
Column() {
Text('多层叠加')
}
.level1()
.level2()
.level3()
// 最终样式:width=200, height=150, backgroundColor='#007DFF', borderRadius=8
}
}
8. 完整实战示例:@Styles 公共样式布局应用
8.1 需求分析
本示例展示一个完整的 @Styles 公共样式布局应用,包含以下功能:
- 全局样式演示:展示全局
@Styles的定义和使用 - 组件内样式演示:展示组件内
@Styles的定义和使用,包括访问状态变量 - 样式叠加演示:展示多个
@Styles的叠加效果 - 按钮样式复用:展示按钮样式的复用和变体
- 动态样式演示:展示基于状态变量的动态样式
8.2 完整代码
/**
* @Styles 公共样式布局示例(API 24)
*
* 核心技术点:
* 1. 全局 @Styles:在组件外定义,可在整个文件中复用
* 2. 组件内 @Styles:在组件内定义,仅在该组件内可用
* 3. @Styles 仅支持通用属性(width/height/padding/backgroundColor/borderRadius/border/shadow/margin 等)
* 4. 多个 @Styles 可叠加调用,后调用的样式会覆盖前面的相同属性
*/
// ==================== 全局 @Styles 定义 ====================
/**
* 全局卡片基础样式
* 包含:宽度、内边距、圆角、背景色、阴影效果(均为通用属性)
*/
@Styles
function globalCardStyle() {
.width('90%')
.padding({ top: 16, right: 16, bottom: 16, left: 16 })
.borderRadius(12)
.backgroundColor('#FFFFFF')
.shadow({ radius: 8, color: '#0000001A', offsetY: 4 })
}
/**
* 全局按钮样式
* 包含:高度、圆角、背景色(均为通用属性)
* 注意:justifyContent/alignItems 是容器布局属性,不能放在 @Styles 中
*/
@Styles
function globalButtonStyle() {
.height(48)
.borderRadius(24)
.backgroundColor('#007DFF')
}
/**
* 全局警告按钮样式
* 包含:警告主题背景色(通用属性)
* 用于叠加覆盖全局按钮样式中的背景色
*/
@Styles
function globalWarningButtonStyle() {
.backgroundColor('#FF6B35')
}
/**
* 全局虚线边框样式
* 包含:虚线边框、内边距(通用属性)
*/
@Styles
function globalDashedBorderStyle() {
.border({ width: 2, color: '#007DFF', style: BorderStyle.Dashed })
.padding({ top: 12, right: 12, bottom: 12, left: 12 })
}
// ==================== 组件定义 ====================
@Entry
@Component
struct Index {
// 组件内状态管理
@State message: string = '点击按钮查看效果';
@State count: number = 0;
@State isDarkMode: boolean = false;
/**
* 组件内 @Styles 定义 - 仅在当前组件内可用
* 计数器卡片样式:自定义背景色和边框(通用属性)
*/
@Styles
componentCounterCardStyle() {
.backgroundColor('#F0F5FF')
.border({ width: 2, color: '#007DFF', style: BorderStyle.Solid })
}
/**
* 组件内 @Styles 定义 - 仅在当前组件内可用
* 消息卡片样式:自定义背景色(通用属性)
*/
@Styles
componentMessageCardStyle() {
.backgroundColor('#E8F5E9')
}
/**
* 组件内 @Styles 定义 - 仅在当前组件内可用
* 紧凑卡片样式:较小的内边距(通用属性)
*/
@Styles
componentCompactCardStyle() {
.padding({ top: 10, right: 10, bottom: 10, left: 10 })
.borderRadius(8)
}
/**
* 组件内 @Styles 定义 - 仅在当前组件内可用
* 动态背景样式:根据状态变量切换背景色
*/
@Styles
componentDynamicBackgroundStyle() {
.backgroundColor(this.isDarkMode ? '#2C2C2C' : '#FFFFFF')
}
build() {
// 主容器:垂直布局,占满屏幕高度
Column({ space: 20 }) {
// ==================== 标题区域 ====================
Text('@Styles 公共样式布局示例')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#1A1A1A')
.margin({ top: 32 })
// ==================== 全局 @Styles 演示 ====================
Column({ space: 12 }) {
Text('全局 @Styles 演示')
.fontSize(20)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
// 使用全局卡片样式
Column({ space: 8 }) {
Text('这是一个使用全局样式的卡片')
.fontSize(16)
.fontColor('#333333')
.fontWeight(FontWeight.Medium)
Text('全局样式特点:\n1. 在组件外定义\n2. 可在整个文件中复用\n3. 仅支持通用属性(width/height/padding/backgroundColor/borderRadius/border/shadow/margin 等)')
.fontSize(14)
.fontColor('#666666')
.textAlign(TextAlign.Start)
}
.globalCardStyle() // 应用全局卡片样式
}
// ==================== 组件内 @Styles 演示 ====================
Column({ space: 12 }) {
Text('组件内 @Styles 演示')
.fontSize(20)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
// 使用全局卡片样式 + 组件内计数器卡片样式(样式叠加)
Column({ space: 8 }) {
Text('计数器卡片')
.fontSize(16)
.fontColor('#333333')
.fontWeight(FontWeight.Medium)
Text(`${this.count}`)
.fontSize(48)
.fontColor('#00B42A')
.fontWeight(FontWeight.Bold)
Text('组件内样式特点:\n1. 在组件内定义\n2. 仅在当前组件内可用\n3. 可与全局样式叠加使用\n4. 可以访问组件的状态变量')
.fontSize(14)
.fontColor('#666666')
.textAlign(TextAlign.Start)
}
.globalCardStyle() // 应用全局卡片样式(基础样式)
.componentCounterCardStyle() // 叠加组件内计数器卡片样式(覆盖背景色,添加边框)
}
// ==================== 样式叠加演示 ====================
Column({ space: 12 }) {
Text('样式叠加演示')
.fontSize(20)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
// 使用全局卡片样式 + 全局虚线边框样式(样式叠加)
Column({ space: 8 }) {
Text('样式叠加效果')
.fontSize(16)
.fontColor('#333333')
.fontWeight(FontWeight.Medium)
Text('多个 @Styles 可叠加调用,后调用的样式会覆盖前面的相同属性')
.fontSize(14)
.fontColor('#666666')
.textAlign(TextAlign.Start)
}
.globalCardStyle() // 基础样式
.globalDashedBorderStyle() // 叠加虚线边框样式
}
// ==================== 按钮样式演示 ====================
Column({ space: 12 }) {
Text('按钮样式复用演示')
.fontSize(20)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
// 使用全局按钮样式的按钮
Column() {
Text('普通按钮')
.fontSize(16)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Medium)
}
.globalButtonStyle() // 应用全局按钮样式
.width('90%')
.justifyContent(FlexAlign.Center) // 布局属性直接设置
.alignItems(HorizontalAlign.Center) // 布局属性直接设置
.onClick(() => {
this.message = '普通按钮被点击!';
})
// 使用全局按钮样式 + 全局警告按钮样式(样式叠加)
Column() {
Text('警告按钮')
.fontSize(16)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Medium)
}
.globalButtonStyle() // 应用全局按钮样式(基础样式)
.globalWarningButtonStyle() // 叠加警告按钮样式(覆盖背景色)
.width('90%')
.justifyContent(FlexAlign.Center) // 布局属性直接设置
.alignItems(HorizontalAlign.Center) // 布局属性直接设置
.onClick(() => {
this.message = '警告按钮被点击!';
})
// 使用全局按钮样式的计数器按钮
Column() {
Text(`点击计数: ${this.count}`)
.fontSize(16)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Medium)
}
.globalButtonStyle() // 应用全局按钮样式
.width('90%')
.justifyContent(FlexAlign.Center) // 布局属性直接设置
.alignItems(HorizontalAlign.Center) // 布局属性直接设置
.onClick(() => {
this.count++;
this.message = `计数器: ${this.count}`;
})
}
// ==================== 动态样式演示 ====================
Column({ space: 12 }) {
Text('动态样式演示')
.fontSize(20)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
// 使用全局卡片样式 + 组件内动态背景样式
Column({ space: 8 }) {
Text(`深色模式: ${this.isDarkMode ? '开启' : '关闭'}`)
.fontSize(16)
.fontColor(this.isDarkMode ? '#FFFFFF' : '#333333')
.fontWeight(FontWeight.Medium)
}
.globalCardStyle() // 基础样式
.componentDynamicBackgroundStyle() // 动态背景样式
// 切换深色模式按钮
Column() {
Text(this.isDarkMode ? '切换到浅色模式' : '切换到深色模式')
.fontSize(16)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Medium)
}
.globalButtonStyle()
.width('90%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.onClick(() => {
this.isDarkMode = !this.isDarkMode;
this.message = `深色模式已${this.isDarkMode ? '开启' : '关闭'}`;
})
}
// ==================== 消息展示区域 ====================
Column({ space: 12 }) {
Text('消息提示')
.fontSize(20)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
// 使用全局卡片样式 + 组件内消息卡片样式(样式叠加)
Column({ space: 8 }) {
Text(this.message)
.fontSize(16)
.fontColor('#007DFF')
.fontWeight(FontWeight.Medium)
}
.globalCardStyle() // 基础样式
.componentMessageCardStyle() // 叠加消息卡片样式(覆盖背景色)
.componentCompactCardStyle() // 叠加紧凑卡片样式(覆盖内边距和圆角)
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F7FA')
.padding({ bottom: 32 })
}
}
8.3 代码解析
全局 @Styles 定义
@Styles
function globalCardStyle() {
.width('90%')
.padding({ top: 16, right: 16, bottom: 16, left: 16 })
.borderRadius(12)
.backgroundColor('#FFFFFF')
.shadow({ radius: 8, color: '#0000001A', offsetY: 4 })
}
该样式定义了卡片的基础样式,包含宽度、内边距、圆角、背景色和阴影效果,这些都是通用属性,可以在任何组件上使用。
组件内 @Styles 定义
@Styles
componentDynamicBackgroundStyle() {
.backgroundColor(this.isDarkMode ? '#2C2C2C' : '#FFFFFF')
}
该样式访问了组件的 isDarkMode 状态变量,根据状态值动态切换背景色,体现了组件内 @Styles 的核心优势——可以访问组件状态。
样式叠加使用
Column() {
Text('消息内容')
}
.globalCardStyle() // 基础样式
.componentMessageCardStyle() // 覆盖背景色
.componentCompactCardStyle() // 覆盖内边距和圆角
通过叠加多个 @Styles,实现了基础样式 + 主题样式 + 紧凑样式的组合效果。
布局属性的正确使用
Column() {
Text('按钮文本')
}
.globalButtonStyle() // 应用按钮样式
.width('90%')
.justifyContent(FlexAlign.Center) // 布局属性直接设置
.alignItems(HorizontalAlign.Center) // 布局属性直接设置
注意 justifyContent 和 alignItems 是布局属性,不能放入 @Styles 中,需要在组件上直接设置。
9. @Styles 与 @Extend 的对比与选型
9.1 核心区别
| 特性 | @Styles | @Extend |
|---|---|---|
| 定义方式 | 使用 function 关键字(全局)或直接定义(组件内) |
使用 @Extend(Component) 语法 |
| 作用域 | 文件级作用域,不支持 export | 支持 export,可跨文件使用 |
| 参数传递 | 不支持 | 支持 |
| 属性限制 | 仅支持通用属性和通用事件 | 支持通用属性和组件特有属性 |
| 访问组件状态 | 组件内 @Styles 支持 | 不支持 |
| 适用场景 | 单个文件内的样式复用 | 跨文件的样式复用、参数化样式 |
9.2 @Extend 语法示例
// 定义扩展样式(支持参数)
@Extend(Text)
function fancyText(fontSizeValue: number, fontColorValue: string) {
.fontSize(fontSizeValue)
.fontColor(fontColorValue)
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Center)
}
@Extend(Column)
function cardWithTheme(themeColor: string) {
.width('90%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.border({ width: 2, color: themeColor, style: BorderStyle.Solid })
}
@Entry
@Component
struct ExtendExample {
build() {
Column({ space: 16 }) {
// 使用扩展样式(带参数)
Text('大标题')
.fancyText(24, '#1A1A1A')
Text('小标题')
.fancyText(18, '#666666')
// 使用带主题的卡片样式
Column() {
Text('蓝色主题卡片')
}
.cardWithTheme('#007DFF')
Column() {
Text('绿色主题卡片')
}
.cardWithTheme('#00B42A')
}
}
}
9.3 选型建议
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 单文件内样式复用 | @Styles | 简单直接,无需导出 |
| 需要访问组件状态 | 组件内 @Styles | 可以访问 @State、@Prop 等状态变量 |
| 跨文件样式复用 | @Extend | 支持 export,可在多个文件中复用 |
| 参数化样式 | @Extend | 支持参数传递,实现灵活定制 |
| 组件特有属性复用 | @Extend | @Styles 不支持组件特有属性 |
| 简单样式组合 | @Styles | 语法更简洁 |
10. @Styles 与 AttributeModifier 的协作
10.1 AttributeModifier 概述
AttributeModifier 是一种更灵活的样式扩展机制,允许开发者通过类的方式定义样式,并支持动态修改和状态响应。
10.2 AttributeModifier 语法示例
// 定义属性修饰器类
class CardModifier extends AttributeModifier<ColumnAttribute> {
private backgroundColor: string = '#FFFFFF'
constructor(bgColor: string) {
super()
this.backgroundColor = bgColor
}
applyNormalAttribute(instance: ColumnAttribute): void {
instance.width('90%')
instance.padding(16)
instance.backgroundColor(this.backgroundColor)
instance.borderRadius(12)
}
}
@Entry
@Component
struct AttributeModifierExample {
@State cardColor: string = '#E3F2FD'
build() {
Column() {
Text('使用 AttributeModifier')
}
.applyModifier(new CardModifier(this.cardColor))
}
}
10.3 三者对比
| 特性 | @Styles | @Extend | AttributeModifier |
|---|---|---|---|
| 定义方式 | 函数式 | 扩展式 | 类式 |
| 参数传递 | 不支持 | 支持 | 支持(通过构造函数) |
| 作用域 | 文件级 | 支持 export | 支持 export |
| 动态性 | 有限(组件内可访问状态) | 有限 | 高(可动态修改) |
| 状态响应 | 组件内 @Styles 支持 | 不支持 | 支持 |
| 复杂度 | 低 | 中 | 高 |
| 适用场景 | 简单样式复用 | 参数化样式 | 复杂动态样式 |
10.4 协作使用示例
// 定义全局 @Styles
@Styles
function baseCardStyle() {
.width('90%')
.padding(16)
.borderRadius(12)
}
// 定义 AttributeModifier
class ThemeModifier extends AttributeModifier<ColumnAttribute> {
constructor(private theme: 'primary' | 'success' | 'warning') {
super()
}
applyNormalAttribute(instance: ColumnAttribute): void {
const colors = {
primary: '#E3F2FD',
success: '#E8F5E9',
warning: '#FFF3E0'
}
instance.backgroundColor(colors[this.theme])
}
}
@Entry
@Component
struct CombinedUsage {
@State currentTheme: 'primary' | 'success' | 'warning' = 'primary'
build() {
Column({ space: 16 }) {
// 组合使用 @Styles 和 AttributeModifier
Column() {
Text('组合样式')
}
.baseCardStyle() // 使用 @Styles 设置基础样式
.applyModifier(new ThemeModifier(this.currentTheme)) // 使用 AttributeModifier 设置动态主题
}
}
}
11. 多态样式 stateStyles 联用技巧
11.1 stateStyles 概述
stateStyles 用于设置组件不同状态下的样式,支持以下状态:
normal:组件无状态时的样式pressed:组件按下状态的样式disabled:组件禁用状态的样式focused:组件获焦状态的样式clicked:组件点击状态的样式
11.2 与 @Styles 联用示例
// 定义不同状态的样式
@Styles
function normalStyle() {
.backgroundColor('#007DFF')
.borderRadius(24)
}
@Styles
function pressedStyle() {
.backgroundColor('#005AFF')
.borderRadius(24)
}
@Styles
function disabledStyle() {
.backgroundColor('#BDBDBD')
.borderRadius(24)
}
@Entry
@Component
struct StateStylesExample {
@State isEnabled: boolean = true
build() {
Column() {
Text('多态样式按钮')
.fontSize(16)
.fontColor('#FFFFFF')
.width(200)
.height(48)
.stateStyles({
normal: normalStyle,
pressed: pressedStyle,
disabled: disabledStyle
})
.enabled(this.isEnabled)
}
}
}
11.3 注意事项
-
stateStyles 仅支持通用属性:与
@Styles一样,stateStyles中也只能包含通用属性和通用事件。 -
Builder 中的多态样式:由于 Builder 不具备独立的自定义父节点,多态样式无法直接在 Builder 中生效。解决方法是将多态样式封装至自定义组件内部,再将此组件置于
@Builder中。 -
焦点态生效条件:多态样式的焦点态只有在焦点激活态开启时生效。
12. 常见踩坑与排查方法
12.1 坑一:组件特有属性放入 @Styles
问题描述:将 fontSize、fontColor、fontWeight 等组件特有属性放入 @Styles 中,导致编译错误。
错误代码:
@Styles
function textStyle() {
.fontSize(16) // 编译错误:fontSize 是 Text 组件特有属性
.fontColor('#333') // 编译错误
}
解决方案:将组件特有属性直接在组件上设置:
@Styles
function commonContainerStyle() {
.width('90%')
.padding(16)
}
Text('示例')
.commonContainerStyle()
.fontSize(16) // 直接在组件上设置
.fontColor('#333')
12.2 坑二:布局属性放入 @Styles
问题描述:将 justifyContent、alignItems 等布局属性放入 @Styles 中,导致编译错误。
错误代码:
@Styles
function flexCenterStyle() {
.justifyContent(FlexAlign.Center) // 编译错误
.alignItems(HorizontalAlign.Center)
}
解决方案:将布局属性直接在容器组件上设置:
@Styles
function containerStyle() {
.width('100%')
.height(100)
}
Column() {
Text('居中内容')
}
.containerStyle()
.justifyContent(FlexAlign.Center) // 直接设置
.alignItems(HorizontalAlign.Center)
12.3 坑三:FontWeight.SemiBold 不存在
问题描述:使用 FontWeight.SemiBold 导致编译错误,提示属性不存在。
错误代码:
Text('示例')
.fontWeight(FontWeight.SemiBold) // 编译错误
解决方案:使用有效的 FontWeight 值:
Text('示例')
.fontWeight(FontWeight.Medium) // 正确
// 或
Text('示例')
.fontWeight(500) // 正确,使用数值
12.4 坑四:@Styles 跨文件使用
问题描述:尝试在一个文件中使用另一个文件定义的 @Styles,导致编译错误。
错误代码:
// styles.ets
@Styles
function cardStyle() {
.width('90%')
}
// index.ets
@Entry
@Component
struct Index {
build() {
Column() {
Text('卡片')
.cardStyle() // 编译错误:cardStyle 未定义
}
}
}
解决方案:使用 @Extend 或 AttributeModifier 实现跨文件样式复用:
// styles.ets
@Extend(Column)
export function cardStyle() {
.width('90%')
.padding(16)
}
// index.ets
import { cardStyle } from './styles'
@Entry
@Component
struct Index {
build() {
Column() {
Text('卡片')
}
.cardStyle() // 正确
}
}
12.5 坑五:@Styles 传入参数
问题描述:尝试为 @Styles 方法传入参数,导致编译错误。
错误代码:
@Styles
function customWidth(widthValue: number) { // 编译错误
.width(widthValue)
}
解决方案:使用组件内 @Styles 访问状态变量,或使用 @Extend:
// 方案一:组件内 @Styles
@Entry
@Component
struct Example {
@State widthValue: number = 100
@Styles
dynamicWidthStyle() {
.width(this.widthValue)
}
}
// 方案二:@Extend
@Extend(Column)
function customWidth(widthValue: number) {
.width(widthValue)
}
12.6 坑六:@Styles 内使用逻辑组件
问题描述:在 @Styles 方法内使用 if、for 等逻辑,导致编译错误。
错误代码:
@Styles
function conditionalStyle() {
if (true) {
.backgroundColor('#FF0000') // 编译错误
}
}
解决方案:将逻辑判断移到组件使用处:
@Styles
function redStyle() {
.backgroundColor('#FF0000')
}
@Styles
function greenStyle() {
.backgroundColor('#00FF00')
}
@Entry
@Component
struct Example {
@State isError: boolean = true
build() {
Column() {
Text('条件样式')
}
.apply(() => {
return this.isError ? redStyle : greenStyle
})
}
}
13. 性能优化建议
13.1 减少 @Styles 方法数量
虽然 @Styles 可以提高代码复用性,但过多的 @Styles 方法会增加样式查找的开销。建议将相关的样式合并为较少的方法。
优化前:
@Styles
function style1() {
.width(100)
}
@Styles
function style2() {
.height(50)
}
@Styles
function style3() {
.backgroundColor('#FFFFFF')
}
Column() {
Text('示例')
}
.style1()
.style2()
.style3()
优化后:
@Styles
function combinedStyle() {
.width(100)
.height(50)
.backgroundColor('#FFFFFF')
}
Column() {
Text('示例')
}
.combinedStyle()
13.2 避免过度叠加
过多的 @Styles 叠加会增加样式计算的复杂度,建议控制叠加层数在 3 层以内。
优化前:
Column() {
Text('示例')
}
.style1()
.style2()
.style3()
.style4()
.style5() // 过多叠加
优化后:
@Styles
function baseStyle() {
.style1()
.style2()
.style3()
}
Column() {
Text('示例')
}
.baseStyle()
.style4() // 控制在 3 层以内
13.3 使用资源文件管理颜色和尺寸
将颜色、尺寸等常量定义在资源文件中,避免在 @Styles 中硬编码。
优化前:
@Styles
function cardStyle() {
.backgroundColor('#FFFFFF') // 硬编码
.borderRadius(12) // 硬编码
}
优化后:
@Styles
function cardStyle() {
.backgroundColor($r('app.color.card_background'))
.borderRadius($r('app.float.card_radius'))
}
13.4 避免在 @Styles 中使用复杂计算
复杂的计算逻辑会在每次样式应用时执行,影响性能。
优化前:
@Styles
function dynamicStyle() {
.width(Math.sqrt(100) * 2) // 复杂计算
}
优化后:
const calculatedWidth = Math.sqrt(100) * 2
@Styles
function dynamicStyle() {
.width(calculatedWidth) // 预先计算
}
14. 最佳实践总结
14.1 样式组织规范
├── src/
│ ├── main/
│ │ ├── ets/
│ │ │ ├── pages/
│ │ │ │ ├── Index.ets # 页面组件
│ │ │ │ └── OtherPage.ets # 其他页面
│ │ │ ├── components/
│ │ │ │ └── CustomComponent.ets # 自定义组件
│ │ │ └── styles/
│ │ │ └── CommonStyles.ets # @Extend 样式(跨文件复用)
│ │ └── resources/
│ │ ├── base/
│ │ │ ├── color/
│ │ │ │ └── color.json # 颜色资源
│ │ │ └── float/
│ │ │ └── float.json # 尺寸资源
14.2 命名规范
| 类型 | 命名规则 | 示例 |
|---|---|---|
| 全局 @Styles | global + 描述 + Style |
globalCardStyle |
| 组件内 @Styles | component + 描述 + Style |
componentCounterCardStyle |
| @Extend | 描述 + 组件名 | cardStyle(扩展 Column) |
14.3 使用建议
- 单文件内样式复用:使用
@Styles,简单直接。 - 需要访问组件状态:使用组件内
@Styles。 - 跨文件样式复用:使用
@Extend。 - 参数化样式:使用
@Extend。 - 复杂动态样式:使用
AttributeModifier。 - 多状态样式:使用
stateStyles配合@Styles。
14.4 注意事项
- 仅使用通用属性:
@Styles内只能包含通用属性和通用事件。 - 避免参数传递:
@Styles不支持参数传递。 - 避免逻辑组件:
@Styles内不能使用if、for等逻辑。 - 文件级作用域:
@Styles只能在当前文件内使用。 - 优先级顺序:组件内 @Styles > 全局 @Styles > 组件直接属性。
结语
@Styles 装饰器是 HarmonyOS ArkTS 中实现样式复用的重要机制,通过合理使用 @Styles,可以显著提高代码的可维护性和开发效率。在实际开发中,应根据具体场景选择合适的样式复用方案,并遵循相关的限制条件和最佳实践,以确保代码的质量和性能。
希望本文能帮助开发者深入理解 @Styles 的使用方法和设计思想,在实际项目中灵活运用,构建出优雅、高效的 UI 代码。
参考资料:
更多推荐




所有评论(0)