

前言
回顾「猫猫大作战」的按钮——「开始游戏」(绿)、「暂停」(灰)、「重新开始」(红)、「再来一局」(绿)、「返回主菜单」(浅灰)。每个按钮都重复写了 width/height/fontSize/borderRadius 这堆属性,代码重复严重,改一处要改 N 处。
HarmonyOS 提供了 AttributeModifier 属性修饰器——把样式抽成可复用类,一处定义、多处复用,还能针对不同状态(正常/按下/禁用)应用不同样式。本篇以「猫猫大作战」按钮样式复用为锚点,把 AttributeModifier 的定义与复用、多状态样式、与 @Styles/@Extend 的取舍三大要点讲透。
提示:本系列不讲 ArkTS 基础语法与环境搭建,假设你已跟完第 1–29 篇。本篇是布局进阶的收尾篇。
一、场景拆解:按钮样式重复
回顾「猫猫大作战」底部栏按钮(第 19 篇):
// 来源:entry/src/main/ets/pages/Index.ets GameView() 底部栏
Button('暂停')
.width(100).height(44)
.fontSize(15).fontColor('#FFFFFF')
.backgroundColor('#95A5A6')
.borderRadius(22)
.onClick(() => { this.pauseGame(); })
Button('重新开始')
.width(100).height(44)
.fontSize(15).fontColor('#FFFFFF')
.backgroundColor('#E74C3C')
.borderRadius(22)
.onClick(() => { this.clearTimers(); this.startGame(); })
痛点:
| 问题 | 表现 |
|---|---|
| 重复代码 | width(100).height(44).fontSize(15)... 每个按钮写一遍 |
| 改一处动全身 | 按钮高度 44 改 48,要全文搜索替换 |
| 多状态难管 | 按下/禁用样式要 onTouch 手动切,代码冗长 |
AttributeModifier 的解法:
// 1. 定义样式类
class SecondaryButtonModifier implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
instance.width(100).height(44).fontSize(15)
.fontColor('#FFFFFF').backgroundColor('#95A5A6').borderRadius(22)
}
}
// 2. 复用
Button('暂停').attributeModifier(new SecondaryButtonModifier())
关键经验:AttributeModifier 把样式抽成类,一处定义多处复用。
二、AttributeModifier 基本用法
2.1 定义样式类
class PrimaryButtonModifier implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
instance
.width('70%')
.height(56)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.backgroundColor('#2ECC71')
.borderRadius(28)
.shadow({ radius: 8, color: 'rgba(46, 204, 113, 0.4)', offsetY: 4 })
}
}
核心要素:
| 要素 | 说明 |
|---|---|
implements AttributeModifier<ButtonAttribute> | 泛型指定目标组件类型 |
applyNormalAttribute(instance) | 正常状态应用样式 |
instance.width(...) | 链式调用,与组件属性一致 |
2.2 应用样式
Button($r('app.string.start_game'))
.attributeModifier(new PrimaryButtonModifier())
.onClick(() => { this.startGame(); })
关键经验:.attributeModifier(new XxxModifier()) 一行应用全套样式。
2.3 三种按钮样式类
// 主操作按钮(绿色,开始游戏)
class PrimaryButtonModifier implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
instance
.width('70%').height(56)
.fontSize(20).fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF').backgroundColor('#2ECC71')
.borderRadius(28)
.shadow({ radius: 8, color: 'rgba(46, 204, 113, 0.4)', offsetY: 4 })
}
}
// 次要操作按钮(灰色,暂停)
class SecondaryButtonModifier implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
instance
.width(100).height(44)
.fontSize(15).fontColor('#FFFFFF')
.backgroundColor('#95A5A6').borderRadius(22)
}
}
// 危险操作按钮(红色,重新开始)
class DangerButtonModifier implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
instance
.width(100).height(44)
.fontSize(15).fontColor('#FFFFFF')
.backgroundColor('#E74C3C').borderRadius(22)
}
}
三、多状态样式
3.1 applyPressedAttribute 按下状态
class PrimaryButtonModifier implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
instance.backgroundColor('#2ECC71') // 正常:亮绿
}
applyPressedAttribute(instance: ButtonAttribute): void {
instance.backgroundColor('#27AE60') // 按下:深绿
}
}
状态回调对照:
| 回调 | 触发时机 | 适用 |
|---|---|---|
applyNormalAttribute | 正常显示 | 默认样式 |
applyPressedAttribute | 按下时 | 按压反馈 |
applyDisabledAttribute | enabled(false) 时 | 禁用样式 |
applyFocusedAttribute | 获得焦点时 | 键盘/遥控器焦点 |
applySelectedAttribute | 选中态 | Toggle/Tab |
3.2 禁用状态样式
class SubmitButtonModifier implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
instance.backgroundColor('#2ECC71').fontColor('#FFFFFF')
}
applyDisabledAttribute(instance: ButtonAttribute): void {
instance.backgroundColor('#BDC3C7').fontColor('#ECF0F1') // 灰色禁用
}
}
// 使用:禁用态自动应用 applyDisabledAttribute
Button('提交').attributeModifier(new SubmitButtonModifier()).enabled(this.isFormValid)
3.3 完整多状态按钮
class FullStateButtonModifier implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
instance
.width(200).height(48)
.fontSize(16).fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.backgroundColor('#2ECC71')
.borderRadius(24)
}
applyPressedAttribute(instance: ButtonAttribute): void {
instance.backgroundColor('#27AE60') // 按下深绿
instance.scale({ x: 0.95, y: 0.95 }) // 按下缩小 5%
}
applyDisabledAttribute(instance: ButtonAttribute): void {
instance.backgroundColor('#BDC3C7') // 禁用灰
instance.fontColor('#ECF0F1')
}
}
执行流程:
- 正常显示 →
applyNormalAttribute - 用户按下 →
applyPressedAttribute(覆盖 normal 的差异属性) - 用户松开 → 回到
applyNormalAttribute enabled(false)→applyDisabledAttribute
四、改造底部栏用 AttributeModifier
4.1 定义三个按钮样式类
// 次要按钮(暂停)
class SecondaryButtonModifier implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
instance
.width(100).height(44)
.fontSize(15).fontColor('#FFFFFF')
.backgroundColor('#95A5A6').borderRadius(22)
}
applyPressedAttribute(instance: ButtonAttribute): void {
instance.backgroundColor('#7F8C8D') // 按下深灰
}
}
// 危险按钮(重新开始)
class DangerButtonModifier implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
instance
.width(100).height(44)
.fontSize(15).fontColor('#FFFFFF')
.backgroundColor('#E74C3C').borderRadius(22)
}
applyPressedAttribute(instance: ButtonAttribute): void {
instance.backgroundColor('#C0392B') // 按下深红
}
}
4.2 改造底部栏
@Builder
GameView() {
Column() {
this.GameHUD()
Column() {
// 预告区
Row() { /* ... */ }
// 游戏棋盘
Stack() { /* 3 层叠 */ }
.width(GameConfig.BOARD_WIDTH * GameConfig.CELL_SIZE)
.height(GameConfig.BOARD_HEIGHT * GameConfig.CELL_SIZE)
.borderRadius(12)
.clip(true)
.backgroundColor('#D6EEF5')
}
.alignItems(HorizontalAlign.Center)
Spacer() // 弹性占位,推底部栏到最底
// 底部控制栏(AttributeModifier 改造)
Row() {
Button($r('app.string.pause'))
.attributeModifier(new SecondaryButtonModifier())
.onClick(() => { this.pauseGame(); })
Spacer() // 撑开两按钮到两端
Button($r('app.string.restart'))
.attributeModifier(new DangerButtonModifier())
.onClick(() => {
this.clearTimers();
this.startGame();
})
}
.width('100%')
.padding({ left: 24, right: 24, bottom: 24, top: 12 })
}
.width('100%').height('100%')
.linearGradient({
direction: GradientDirection.Bottom,
colors: [['#E8F4F8', 0.0], ['#D6EEF5', 0.5], ['#C9E8F2', 1.0]]
})
.alignItems(HorizontalAlign.Center)
}
改造对比:
| 维度 | 原版(属性堆叠) | AttributeModifier 版 |
|---|---|---|
| 代码量 | 每按钮 7 行属性 | 每按钮 1 行 modifier |
| 按压反馈 | 无(默认 stateEffect) | applyPressedAttribute 自定义 |
| 改样式 | 改每个按钮的属性 | 改 Modifier 类,全应用生效 |
| 复用 | 无法复用 | 一处定义多处用 |
五、AttributeModifier vs @Styles vs @Extend
ArkUI 有三种样式复用机制:
5.1 @Styles 通用样式
@Styles
function primaryButtonStyle() {
.width('70%').height(56)
.fontSize(20).fontWeight(FontWeight.Bold)
}
Button('开始').primaryButtonStyle()
5.2 @Extend 特定组件样式
@Extend(Button)
function primaryButtonExtend() {
.width('70%').height(56)
.backgroundColor('#2ECC71').borderRadius(28)
}
Button('开始').primaryButtonExtend()
5.3 三者对比
| 机制 | 适用组件 | 多状态 | 跨组件复用 | 推荐 |
|---|---|---|---|---|
@Styles | 任意 | ❌ | ✅ | 简单通用样式 |
@Extend | 指定组件 | ❌ | ❌ | 特定组件样式 |
AttributeModifier | 任意 | ✅ | ✅ | 复杂多状态样式 |
实战经验:简单样式用 @Styles,特定组件用 @Extend,多状态/复杂复用用 AttributeModifier。
5.4 选择决策树
样式需要多状态(按下/禁用)?
├─ 是 → AttributeModifier
└─ 否
└─ 样式跨多种组件复用?
├─ 是 → @Styles
└─ 否
└─ 样式只针对某组件? → @Extend
六、踩坑提示
6.1 AttributeModifier 泛型不匹配
// ❌ 错误:Modifier 声明 ButtonAttribute,但用在 Text 上
class MyModifier implements AttributeModifier<ButtonAttribute> { /* ... */ }
Text('A').attributeModifier(new MyModifier()) // 类型不匹配
// ✅ 正确:泛型与组件匹配
class MyModifier implements AttributeModifier<TextAttribute> { /* ... */ }
Text('A').attributeModifier(new MyModifier())
6.2 忘记 implements AttributeModifier
// ❌ 错误:没 implements,attributeModifier 不接受
class MyModifier { applyNormalAttribute() {} }
// ✅ 正确:implements AttributeModifier<T>
class MyModifier implements AttributeModifier<ButtonAttribute> { /* ... */ }
6.3 Modifier 与组件属性冲突
// ⚠️ Modifier 设了 backgroundColor,组件又设了一次,后者覆盖前者
Button('A')
.attributeModifier(new MyModifier()) // 设 backgroundColor('#2ECC71')
.backgroundColor('#E74C3C') // 覆盖 Modifier 的绿色
// ✅ 推荐:样式统一在 Modifier 里管,组件只设业务属性
Button('A')
.attributeModifier(new MyModifier())
.onClick(() => { /* ... */ })
6.4 applyPressedAttribute 没覆盖 normal 的所有差异
class MyModifier implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
instance.backgroundColor('#2ECC71').scale({ x: 1, y: 1 })
}
applyPressedAttribute(instance: ButtonAttribute): void {
instance.backgroundColor('#27AE60') // 只改了颜色,scale 还是 1
// 想要按下缩小,要显式设 scale
instance.scale({ x: 0.95, y: 0.95 })
}
}
七、调试技巧
- 临时加
console.info在 applyNormalAttribute 里,追样式应用时机。 - 按压无反馈:检查是否实现了
applyPressedAttribute;检查组件是否enabled(false)。 - 样式不生效:检查 Modifier 泛型是否与组件匹配;检查是否被组件属性覆盖。
- 多 Modifier 冲突:一个组件只能用一个
attributeModifier,多个样式要合并到一个 Modifier 类。
八、性能与最佳实践
- 复杂多状态样式用 AttributeModifier——一处定义多处复用。
- 泛型与组件匹配——ButtonAttribute 配 Button,TextAttribute 配 Text。
- 样式统一在 Modifier 管——组件只设业务属性(onClick、state)。
- 简单通用样式用 @Styles——AttributeModifier 杀鸡用牛刀。
- 状态回调全覆盖——normal/pressed/disabled 都实现,避免状态残留。
- Modifier 类可继承复用——
class PrimaryModifier extends BaseModifier。
九、阶段一收尾:UI 组件与布局实战总结(1-30)
本篇是阶段一「UI 组件与布局实战(1-30)」的收尾篇。回顾阶段一完整覆盖:
| 篇 | 主题 | 核心组件 |
|---|---|---|
| 1-5 | 主菜单排版 | Text、Button、Spacer、Column |
| 6-10 | 主菜单视觉 | linearGradient、justifyContent、Row、Stack、if/else |
| 11-12 | 游戏页 HUD + 预告 | GameHUD、Circle 预告 |
| 13-14 | 棋盘背景 | ForEach 双层循环、隔行变色 |
| 15-16 | 猫咪渲染 | Emoji Text、Circle 背景、position 绝对定位 |
| 17 | 列点击层 | 透明 onClick 覆盖层 |
| 18-19 | 底部控制栏 | Row 双按钮、Button 颜色与圆角 |
| 20 | SymbolGlyph | 系统符号字体替代 Emoji |
| 21 | RelativeContainer | 扁平复杂布局 |
| 22 | Blank | 替代 Spacer 的间距 |
| 23 | layoutWeight | 按比例分配宽度 |
| 24 | displayPriority | 窄屏自适应隐藏 |
| 25 | Scroller | 长内容滚动容器 |
| 26 | Badge | 消息角标 |
| 27 | Image 资源 | $r 与 $rawfile |
| 28 | 暗色模式 | 深色资源与 colorMode |
| 29 | 多语言 i18n | string.json 多语言 |
| 30(本篇) | AttributeModifier | 按钮样式复用 |
阶段一核心收获:
- ArkUI 声明式 UI:组件 + 属性 + 事件三件套。
- 布局容器:Column/Row/Stack/RelativeContainer 四大布局。
- 状态驱动:@State 改变 → build() 重渲染。
- 资源系统:$r 编译期资源 + $rawfile 原始文件。
- 多端适配:暗色模式、多语言、多分辨率。
- 样式复用:@Styles/@Extend/AttributeModifier 三层方案。
接下来阶段二(31-70)将进入状态管理 + 交互 + 动画深水区:@State/@Link/@Provide/@Consume 状态管理、@Watch 监听、setInterval 主循环、animateTo 显式动画、@Reusable 列表复用、LazyForEach 大列表等。
总结
本篇我们从 AttributeModifier 切入,掌握了样式类定义与复用、多状态样式(normal/pressed/disabled)、与 @Styles/@Extend 的取舍三大要点,并给出了底部栏 AttributeModifier 改造完整代码。核心要点:AttributeModifier 一处定义多处复用;多状态用 applyPressedAttribute 等;泛型与组件匹配。
下一篇我们将进入阶段二,拆解 @State 单组件状态——得分驱动 UI。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- 「猫猫大作战」项目源码:本仓库
entry/src/main/ets/pages/Index.ets - AttributeModifier 属性修饰器官方指南
- @Styles 样式复用官方指南
- @Extend 扩展官方指南
- ArkUI 样式复用最佳实践
- 开源鸿蒙跨平台社区
- HarmonyOS 开发者官方文档首页
- 系列索引:本仓库
articles/INDEX.md
应用不同样式&spm=1001.2101.3001.5002&articleId=163253162&d=1&t=3&u=e07dc141f05a4fa58f29204cfb610030)

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



