一、背景
在鸿蒙开发中,样式复用是提升开发效率,保证UI一致性的核心需求。常见的实现方式有@Styles、@Extend和stateStyles,但面对多组件适配、多状态统一管理、跨模块复用场景时,官方更推荐使用AttributeModifier实现灵活可扩展的样式复用方案。
二、传统样式复用方案
2.1、@Styles装饰器:组件内/全局静态样式复用
@Styles装饰器可以将多条样式设置提炼成一个方法,直接在组件声明的位置调用。
2.1.1 核心规则与限制
- 支持组件内定义和全局定义两种形式:
- 组件内
@Styles可访问组件内部状态(如this.heightValue),仅当前组件可用;- 全局
@Styles不可访问组件状态,可在整个项目中复用。- 不支持参数传递,样式固定,无法动态调整(如不能通过传参修改背景色、宽高等);
- 不支持导出 / 导入,全局
@Styles虽可跨组件使用,但无法通过export/import在不同模块(如 Entry、HAR)间复用;- 支持关联组件事件(如
onClick),但事件逻辑与样式耦合,复用性受限。2.1.2、使用场景
适用于项目内样式固定、无动态调整需求的场景
// 定义在全局的@Styles封装的样式
@Styles
function globalFancy () {
.width(150)
.height(100)
.backgroundColor(Color.Pink)
}
@Entry
@Component
struct FancyUse {
@State heightValue: number = 100;
// 定义在组件内的@Styles封装的样式
@Styles fancy() {
.width(200)
.height(this.heightValue)
.backgroundColor(Color.Yellow)
.onClick(() => {
this.heightValue = 200;
})
}
build() {
Column({ space: 10 }) {
// 使用全局的@Styles封装的样式
Text('FancyA')
.globalFancy()
.fontSize(30)
// 使用组件内的@Styles封装的样式
Text('FancyB')
.fancy()
.fontSize(30)
}
}
}
2.2、@Extend装饰器:组件专属的参数化样式复用
@Extend,用于扩展组件样式,支持参数传递,是@Styles的增强版,仅适用于单个组件的个性化复用
2.2.1、使用规则与限制
- 仅支持全局定义,不允许在组件内部定义;
- 支持参数传递(如颜色、尺寸、权重等),可动态调整样式;
- 与组件强绑定,仅能被目标组件使用(如
@Extend(Text)定义的样式,只能用于 Text 组件);- 不支持导出 / 导入,无法跨模块(如 HAR 包)复用;
- 支持结合
stateStyles实现多态样式(如按压态、焦点态)
2.2.2、使用场景
适用于单个组件需要动态调整样式的场景(如不同权重的文本、不同颜色的按钮、带参数的图标样式)
@Extend(Text)
function fancyText(weightValue: number, color: Color) {
.fontStyle(FontStyle.Italic)
.fontWeight(weightValue)
.backgroundColor(color)
}
@Entry
@Component
struct Index {
@State label: string = 'Hello World';
build() {
Row({ space: 10 }) {
Text(`${this.label}`)
.fancyText(100, Color.Blue)
Text(`${this.label}`)
.fancyText(200, Color.Pink)
Text(`${this.label}`)
.fancyText(300, Color.Orange)
}.margin('20%')
}
}
2.3、stateStyles:多态样式
stateStyles是组件的属性方法,可根据组件内部状态(如按压、焦点、禁用)动态切换样式,类似于 CSS 伪类,适用于状态关联的样式复用。
2.3.1 支持的组件状态
ArkUI 提供 6 种核心状态,覆盖大部分交互场景:
| 状态名 | 说明 |
|---|---|
normal | 组件默认状态(无交互、无焦点) |
pressed | 组件被按压时的状态 |
focused | 组件获得焦点时的状态(如 Tab 键选中) |
disabled | 组件被禁用时的状态(enabled=false) |
selected | 组件被选中时的状态(如单选框选中) |
active | 组件处于激活态时的状态(如滑动组件激活) |
2.3.2 核心规则与限制
- 仅支持在组件内部直接使用,或结合
@Extend全局复用,不支持单独导出;- 状态样式与组件强绑定,无法跨组件复用(如 Button 的
stateStyles不能直接用于 Text);- 不支持参数传递,样式需硬编码或引用全局资源。
2.3.1、使用场景
适用于组件需要根据交互状态切换样式的场景(如按钮按压变色、输入框获焦边框高亮、禁用态灰色显示)
@Entry
@Component
struct StateStylesSample {
build() {
Column() {
Button('Button1')
.stateStyles({
focused: {
.backgroundColor('#ffffeef0')
},
pressed: {
.backgroundColor('#ff707070')
},
normal: {
.backgroundColor('#ff2787d9')
}
})
.margin(20)
Button('Button2')
.stateStyles({
focused: {
.backgroundColor('#ffffeef0')
},
pressed: {
.backgroundColor('#ff707070')
},
normal: {
.backgroundColor('#ff2787d9')
}
})
}.margin('30%')
}
}
@Extend与stateStyles一起使用
@Extend(Text)
function bottomButtonStyle(btnBgColor: ResourceColor, enable: boolean, disableBgColor?: ResourceColor) {
.width('100%')
.borderRadius(8)
.fontColor(Color.White)
.fontSize(18)
.enabled(enable)
.textAlign(TextAlign.Center)
.height('100%')
.stateStyles({
normal: {
.backgroundColor(btnBgColor)
},
pressed: {
.backgroundColor(disableBgColor ?? btnBgColor)
}
})
.fontWeight(FontWeight.Medium)
}
@Entry
@Component
struct Index {
build() {
Row({ space: 10 }) {
Text('hello word')
.bottomButtonStyle('#0165b8', true, '#ff707070')
}.margin('20%')
}
}
三、AttributeModifier:通用样式复用方案
AttributeModifier是鸿蒙提供的属性修改器接口,可将多组件、多状态的样式封装为独立类,支持配置化传递、跨模块复用,是复杂场景的最优解。
3.1 实现原理
通过实现AttributeModifier<T>接口(T为目标组件的属性类型,如ButtonAttribute、TextAttribute),将不同状态的样式封装在对应方法中,最终通过组件的attributeModifier()方法应用样式。核心是 “样式与组件解耦、配置化动态传递”。
3.2、接口定义
declare interface AttributeModifier<T> {
applyNormalAttribute?(instance: T): void; //默认态
applyPressedAttribute?(instance: T): void; //按压态
applyFocusedAttribute?(instance: T): void; //焦点态
applyDisabledAttribute?(instance: T): void; //禁用态
applySelectedAttribute?(instance: T): void; //选择态
}
3.3、开发步骤
步骤 1:定义通用样式接口(可选,优化配置传递)
定义统一接口规范样式字段,支持配置化传递(避免硬编码,提升灵活性)。
实际开发过程中,可以替换为真实的数据接口
export interface GlobalStyle {
width?: number | string;
height?: number | string;
bgColor?: string;
pressedBgColor?: string;
disabledBgColor?: string;
textColor?: string;
disabledTextColor?: string;
fontSize?: number;
borderRadius?: number;
}
步骤 2:实现 AttributeModifier 接口
封装多状态样式,支持从外部接收配置参数(如GlobalStyle对象),动态调整样式。
import { GlobalStyle } from '../model/styleModel';
export class ButtonStyleModifier implements AttributeModifier<ButtonAttribute> {
private style: Required<GlobalStyle>; // 用 Required 确保所有字段都有值(无 undefined)
// 构造函数:参数默认值设为 {},手动合并默认值与外部配置
constructor(style: GlobalStyle = {}) {
// 核心:逐个字段赋值,用 ?? 兜底默认值(无 ... 运算符)
this.style = {
width: style.width ?? '100%', // 默认宽度:占满父容器
height: style.height ?? 44, // 默认高度:常用按钮高度
bgColor: style.bgColor ?? '#1890ff', // 默认主题蓝
pressedBgColor: style.pressedBgColor ?? '#096dd9', // 按压加深色
disabledBgColor: style.disabledBgColor ?? '#cccccc', // 默认禁用背景
textColor: style.textColor ?? '#ffffff', // 默认白色文字
disabledTextColor: style.disabledTextColor ?? '#999999', // 默认禁用文字色
fontSize: style.fontSize ?? 16, // 默认字号 16px
borderRadius: style.borderRadius ?? 8 // 默认圆角 8px
};
}
// 默认态:直接使用 this.style(已确保所有字段有值,无需非空断言)
applyNormalAttribute(instance: ButtonAttribute): void {
instance
.width(this.style.width)
.height(this.style.height)
.backgroundColor(this.style.bgColor)
.fontColor(this.style.textColor)
.fontSize(this.style.fontSize)
.borderRadius(this.style.borderRadius);
}
// 按压态:使用已兜底的 pressedBgColor
applyPressedAttribute(instance: ButtonAttribute): void {
instance.backgroundColor(this.style.pressedBgColor);
}
// 禁用态:支持自定义禁用色,无则用默认
applyDisabledAttribute(instance: ButtonAttribute): void {
instance
.backgroundColor(this.style.disabledBgColor)
.fontColor(this.style.disabledTextColor);
}
}
步骤 3:组件中应用修饰器
创建AttributeModifier实例,传递自定义样式配置,应用到目标组件。
import { GlobalStyle } from '../model/styleModel';
import { ButtonStyleModifier } from '../utils/MyButtonModifier';
@Entry
@Component
struct Index {
// 自定义样式:仅传需要修改的字段,其他用默认
private submitBtnStyle: GlobalStyle = {
width: 180,
height: 48,
bgColor: '#00b42a',
pressedBgColor: '#00871c',
fontSize: 18
};
// 自定义禁用态样式(示例)
private disabledBtnStyle: GlobalStyle = {
bgColor: '#1890ff',
disabledBgColor: '#87ceeb', // 自定义禁用背景(天蓝色)
disabledTextColor: '#ffffff' // 自定义禁用文字(白色)
};
build() {
Column({ space: 12 }) {
// 1. 默认样式按钮:无需传参,使用全部默认值
Button('默认样式按钮')
.attributeModifier(new ButtonStyleModifier())
// 2. 自定义提交按钮:传递部分字段,覆盖默认值
Button('自定义提交按钮')
.attributeModifier(new ButtonStyleModifier(this.submitBtnStyle))
// 3. 禁用按钮:传递自定义禁用态样式
Button('禁用按钮')
.attributeModifier(new ButtonStyleModifier(this.disabledBtnStyle))
.enabled(false)
}.padding(20)
}
}
四、四种样式复用方案对比与选择建议
| 对比维度 | @Styles | @Extend | stateStyles | AttributeModifier |
|---|---|---|---|---|
| 定义范围 | 组件内 / 全局 | 仅全局 | 组件内 / 结合 @Extend 全局 | 全局(支持导出) |
| 支持参数传递 | 不支持 | 支持 | 不支持 | 支持(配置化传递) |
| 跨组件复用 | 全局可跨组件 | 仅目标组件可用 | 组件专属 | 支持多组件(泛型适配) |
| 跨模块复用(导出 / 导入) | 不支持 | 不支持 | 不支持 | 支持(HAR/Entry 间) |
| 多状态支持(按压 / 禁用等) | 需手动绑定事件 | 可结合 stateStyles | 原生支持 | 接口原生支持 |
| 样式动态调整 | 仅组件内可关联状态 | 参数化动态调整 | 硬编码 / 资源引用 | 配置化动态调整 |
| ✅ 选择建议 | 简单静态样式复用(如固定标题、卡片容器) | 单个组件动态样式(如不同颜色 / 尺寸的文本、按钮) | 组件多状态切换(如按压变色、禁用态) |
复杂场景复用(多组件适配、跨模块复用、配置化样式) 多端适配 / 分层架构项目(如 Entry+HAR 分层) |

1315

被折叠的 条评论
为什么被折叠?



