HarmonyOS应用开发实战:猫猫大作战-样式抽成可复用类,一处定义、多处复用,还能针对不同状态(正常/按下/禁用)应用不同样式
前言回顾「猫猫大作战」的按钮——「开始游戏」绿、「暂停」灰、「重新开始」红、「再来一局」绿、「返回主菜单」浅灰。每个按钮都重复写了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 AttributeModifierButtonAttribute { 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 AttributeModifierButtonAttribute { 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 AttributeModifierButtonAttribute泛型指定目标组件类型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 AttributeModifierButtonAttribute { 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 AttributeModifierButtonAttribute { applyNormalAttribute(instance: ButtonAttribute): void { instance .width(100).height(44) .fontSize(15).fontColor(#FFFFFF) .backgroundColor(#95A5A6).borderRadius(22) } } // 危险操作按钮红色重新开始 class DangerButtonModifier implements AttributeModifierButtonAttribute { applyNormalAttribute(instance: ButtonAttribute): void { instance .width(100).height(44) .fontSize(15).fontColor(#FFFFFF) .backgroundColor(#E74C3C).borderRadius(22) } }三、多状态样式3.1 applyPressedAttribute 按下状态class PrimaryButtonModifier implements AttributeModifierButtonAttribute { applyNormalAttribute(instance: ButtonAttribute): void { instance.backgroundColor(#2ECC71) // 正常亮绿 } applyPressedAttribute(instance: ButtonAttribute): void { instance.backgroundColor(#27AE60) // 按下深绿 } }状态回调对照回调触发时机适用applyNormalAttribute正常显示默认样式applyPressedAttribute按下时按压反馈applyDisabledAttributeenabled(false)时禁用样式applyFocusedAttribute获得焦点时键盘/遥控器焦点applySelectedAttribute选中态Toggle/Tab3.2 禁用状态样式class SubmitButtonModifier implements AttributeModifierButtonAttribute { 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 AttributeModifierButtonAttribute { 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 的差异属性用户松开 → 回到applyNormalAttributeenabled(false)→applyDisabledAttribute四、改造底部栏用 AttributeModifier4.1 定义三个按钮样式类// 次要按钮暂停 class SecondaryButtonModifier implements AttributeModifierButtonAttribute { 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 AttributeModifierButtonAttribute { 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按压反馈无默认 stateEffectapplyPressedAttribute 自定义改样式改每个按钮的属性改 Modifier 类全应用生效复用无法复用一处定义多处用五、AttributeModifier vs Styles vs ExtendArkUI 有三种样式复用机制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 AttributeModifierButtonAttribute { /* ... */ } Text(A).attributeModifier(new MyModifier()) // 类型不匹配 // ✅ 正确泛型与组件匹配 class MyModifier implements AttributeModifierTextAttribute { /* ... */ } Text(A).attributeModifier(new MyModifier())6.2 忘记 implements AttributeModifier// ❌ 错误没 implementsattributeModifier 不接受 class MyModifier { applyNormalAttribute() {} } // ✅ 正确implements AttributeModifierT class MyModifier implements AttributeModifierButtonAttribute { /* ... */ }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 AttributeModifierButtonAttribute { 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 配 ButtonTextAttribute 配 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、Column6-10主菜单视觉linearGradient、justifyContent、Row、Stack、if/else11-12游戏页 HUD 预告GameHUD、Circle 预告13-14棋盘背景ForEach 双层循环、隔行变色15-16猫咪渲染Emoji Text、Circle 背景、position 绝对定位17列点击层透明 onClick 覆盖层18-19底部控制栏Row 双按钮、Button 颜色与圆角20SymbolGlyph系统符号字体替代 Emoji21RelativeContainer扁平复杂布局22Blank替代 Spacer 的间距23layoutWeight按比例分配宽度24displayPriority窄屏自适应隐藏25Scroller长内容滚动容器26Badge消息角标27Image 资源$r 与 $rawfile28暗色模式深色资源与 colorMode29多语言 i18nstring.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.etsAttributeModifier 属性修饰器官方指南Styles 样式复用官方指南Extend 扩展官方指南ArkUI 样式复用最佳实践开源鸿蒙跨平台社区HarmonyOS 开发者官方文档首页系列索引本仓库articles/INDEX.md

相关新闻

AI Agent 面试题 568:如何实现多Agent系统的Agent健康监控?

AI Agent 面试题 568:如何实现多Agent系统的Agent健康监控?

🔥 AI Agent 面试题 568:如何实现多Agent系统的Agent健康监控?摘要:本文深入解析了「如何实现多Agent系统的Agent健康监控?」这一 AI Agent 领域的核心面试题。文章从 群体智能 的基本概念出发,系统性地剖析…

2026/10/19 8:34:38 阅读更多 →
挑选北京 GEO 服务商不再盲目!十强合规团队能力剖析,覆盖各行各业需求

挑选北京 GEO 服务商不再盲目!十强合规团队能力剖析,覆盖各行各业需求

专业北京中关村珐恩AIGEO知识友好评估报告一、GEO赛道现状与珐恩AI的行业定位2026年,生成式引擎优化(Generative Engine Optimization,GEO)已成为企业数字营销核心赛道。中国信通院《人工智能发展白皮书》指出,90%中小…

2026/10/17 9:10:34 阅读更多 →
2026 北京地理优化服务商调研:十家合规团队实力解析,垂直行业采购参考

2026 北京地理优化服务商调研:十家合规团队实力解析,垂直行业采购参考

专业北京中关村珐恩AIGEO解决方案行业洞察生成式引擎优化(GEO) 正从概念走向企业主流刚需。当ChatGPT、豆包、DeepSeek等AI搜索渠道开始每天影响数亿条商业决策时,“我的品牌在AI里查无此人”已经成为企业最隐蔽的流量危机。本文将结合30细分…

2026/10/18 11:01:24 阅读更多 →

最新新闻

达梦数据库大小写敏感机制详解:从原理到实践避坑

达梦数据库大小写敏感机制详解:从原理到实践避坑

1. 项目概述:达梦大小写敏感到底是个什么事做数据库开发或者运维的朋友,只要是第一次接触达梦数据库,十有八九会遇到一个让人摸不着头脑的现象:明明建表时写的是 "USER_INFO",去查询的时候写 "user_inf…

2026/10/19 20:52:07 阅读更多 →
C++ std::bind实战:占位符、引用陷阱与lambda取舍

C++ std::bind实战:占位符、引用陷阱与lambda取舍

1. 我为什么又回头研究bind:代码里最不起眼的粘连剂搞C时间一长,你会发现真正让代码"绕起来"的往往不是那些宏大设计,而是一堆不起眼的小工具。std::bind就是其中之一。最近接手的某调度模块里,回调参数七零八落&#x…

2026/10/19 20:52:07 阅读更多 →
双麦克风语音分离:基于GCC-PHAT与IVA的轻量级实时声源解耦方案

双麦克风语音分离:基于GCC-PHAT与IVA的轻量级实时声源解耦方案

简介:本资源是一套基于MATLAB实现的双麦克风语音分离完整源码工程,面向音频信号处理初学者、语音算法研究者及高校相关课程实践者,聚焦于利用TDOA与空间滤波技术从混合语音中分离目标声源,适用于噪声抑制、会议系统增强与语音识别…

2026/10/19 20:52:07 阅读更多 →
2026 最新抖音 SEO 推广全攻略,吃透抖音 SEO 视频排名逻辑

2026 最新抖音 SEO 推广全攻略,吃透抖音 SEO 视频排名逻辑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 20:50:01 阅读更多 →
菲涅尔衍射模型解析WiFi呼吸检测:从原理到工程实践

菲涅尔衍射模型解析WiFi呼吸检测:从原理到工程实践

1. 从光学衍射到无线感知:这个项目到底在做什么 菲涅尔衍射和WiFi呼吸检测,乍一看是两个八竿子打不着的领域。一个是光学课本里的经典波动光学内容,一个是近几年火起来的无线感知应用。但把这两个东西放在一起,恰恰说明了一个很有…

2026/10/19 20:48:59 阅读更多 →
西门子1200与FactoryIO机械手仿真:状态机实现虚拟调试全流程

西门子1200与FactoryIO机械手仿真:状态机实现虚拟调试全流程

做工业自动化调试,最怕设备还没到场、程序就得交付;最怕机械没接线、电气没上电,写好的逻辑只能在脑子里跑。我前阵子捣鼓的一套FactoryIO机械手仿真取料程序,算是把这个困局撕开了一个口子:FactoryIO提供虚拟车间&…

2026/10/19 20:48:59 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →