HarmonyOS ArkTS 多态样式 stateStyles:把“按下 / 获焦 / 禁用”的样式一次性管住(详细笔记 + 可直接用的模板)

24 阅读6分钟

HarmonyOS ArkTS 多态样式 stateStyles:把“按下 / 获焦 / 禁用”的样式一次性管住(详细笔记 + 可直接用的模板)

一起来构建生态吧~

这篇是我按官方文档 ArkTS 多态样式(stateStyles) 的思路,结合实际写页面时最常踩的坑整理的一份“像人写的”Markdown 博客/笔记。 目标很明确:让你写按钮、输入框、列表项时,不用再手动 onTouch/onHover 去改颜色,而是让组件“自己根据状态切换样式”。


1. 我为什么建议你认真学一下 stateStyles

写 UI 的时候,最烦的往往不是布局,而是这些“细碎但到处都有”的状态表现:

  • 按钮 按下 时背景要变深一点
  • 组件 获焦 时要有高亮(电视/车机/键盘操作尤其明显)
  • 禁用 状态要变灰、透明、不可交互感要明确
  • 列表项 选中 之后要变色,松开还要回到正常

如果你用“传统思路”去写,很容易变成这样:

  • onTouch 里改颜色
  • onFocus 里改边框
  • onHover 里改透明度
  • 再配一堆 @State 去保存当前颜色/样式

结果就是:页面逻辑被样式状态打烂了

stateStyles 的价值是:

把“状态 → 样式”做成一张表,交给系统自动切换。 你只写规则,不用写过程。


2. stateStyles 到底是什么(用一句话讲明白)

stateStyles 是 ArkUI 的一种“多态样式”机制: 它允许你给一个组件配置多套样式,并指定在不同状态下分别生效。

你可以把它理解成 ArkTS 版的:

  • CSS 的 :active / :focus / :disabled / :checked(但写法不是 CSS)
  • 更像“组件内置状态机 + 你提供样式映射表”

3. 它能管哪些状态?(常用就这几个)

不同组件支持的状态不完全一样,但项目里最常用的是:

  • normal:正常状态
  • pressed:按下状态(触控/按键按下)
  • focused:获焦状态(键盘/遥控器/车机/TV 特别重要)
  • disabled:禁用状态
  • selected:选中状态(列表项、Tab、可选择组件)

你要记住一个很现实的点:

stateStyles 不是“你想写哪个状态都行”,而是“组件支持哪些状态你才能用哪些”。 比如纯展示的 Text,可能就没有 pressed/focused 这种完整交互态。


4. 最基础的写法(先把格式吃透)

我建议你先把这个“模板结构”记牢:

 Button('提交')
   .stateStyles({
     normal: {
       // 这里写 normal 状态下的样式链
     },
     pressed: {
       // pressed 状态下的样式链
     },
     focused: {
       // focused 状态下的样式链
     },
     disabled: {
       // disabled 状态下的样式链
     }
   })

它的核心就是: 对象 key 是状态名,value 是该状态下要应用的样式集合


5. 实战 1:按钮的按下态 / 获焦态(最常用,没有之一)

按钮一般至少要做两件事:

  1. pressed 时颜色变深(给用户反馈)
  2. focused 时有明显高亮(多设备、键盘操作更顺)
 @Entry
 @Component
 struct StateStylesButtonDemo {
   build() {
     Column({ space: 12 }) {
       Button('普通按钮')
         .height(44)
         .width('100%')
         .stateStyles({
           normal: {
             .backgroundColor('#2F7BFF')
             .fontColor('#FFFFFF')
             .borderRadius(12)
           },
           pressed: {
             .backgroundColor('#1F5FE0') // 按下更深一点
           },
           focused: {
             .border({ width: 2, color: '#FFCC00' }) // 获焦描边
           },
           disabled: {
             .backgroundColor('#CFCFCF')
             .fontColor('#8A8A8A')
           }
         })
 ​
       Button('禁用按钮')
         .height(44)
         .width('100%')
         .enabled(false)
         .stateStyles({
           normal: {
             .backgroundColor('#2F7BFF')
             .fontColor('#FFFFFF')
             .borderRadius(12)
           },
           disabled: {
             .backgroundColor('#CFCFCF')
             .fontColor('#8A8A8A')
           }
         })
     }
     .padding(16)
   }
 }

我写按钮 stateStyles 的小习惯

  • normal 写全量(背景、字体、圆角)
  • pressed/focused/disabled 尽量只写“差异项”
  • 不要每个状态都重复一堆属性,不然后面维护会痛苦

6. 实战 2:输入框获焦态(比你想象中更重要)

输入框的“获焦表现”是用户体验的关键点,尤其在:

  • 表单页
  • 登录页
  • TV/车机(焦点移动主要靠 focused)

示例(以 TextInput 思路写,重点看 focused/normal 的边框变化):

 @Entry
 @Component
 struct StateStylesInputDemo {
   @State text: string = ''
 ​
   build() {
     Column({ space: 10 }) {
       Text('账号')
         .fontSize(14)
         .fontColor('#333')
 ​
       TextInput({ text: this.text, placeholder: '请输入账号' })
         .height(44)
         .width('100%')
         .padding({ left: 12, right: 12 })
         .stateStyles({
           normal: {
             .border({ width: 1, color: '#DDDDDD' })
             .borderRadius(10)
             .backgroundColor('#FFFFFF')
           },
           focused: {
             .border({ width: 2, color: '#2F7BFF' })
           },
           disabled: {
             .backgroundColor('#F3F3F3')
           }
         })
     }
     .padding(16)
     .backgroundColor('#F6F7F9')
     .height('100%')
   }
 }

你会发现: 用 stateStyles 后,你根本不用写 onFocus/onBlur 去切换边框色


7. 实战 3:列表项选中态 + 按下态(做设置页/菜单页很香)

比如“设置页”里一行行的 item:

  • pressed:按下有反馈
  • selected:选中后高亮(或显示对勾)
 @Entry
 @Component
 struct StateStylesListItemDemo {
   @State selectedIndex: number = 0
 ​
   build() {
     Column() {
       ForEach([0, 1, 2, 3], (i: number) => {
         Row() {
           Text(`选项 ${i + 1}`)
             .fontSize(16)
             .fontColor('#222')
           Blank()
           if (this.selectedIndex === i) {
             Text('✓').fontColor('#2F7BFF')
           }
         }
         .height(52)
         .width('100%')
         .padding({ left: 16, right: 16 })
         .stateStyles({
           normal: {
             .backgroundColor('#FFFFFF')
           },
           pressed: {
             .backgroundColor('#F1F3F5')
           },
           selected: {
             .backgroundColor('#EAF2FF')
           },
           focused: {
             .border({ width: 2, color: '#FFCC00' })
           }
         })
         .onClick(() => {
           this.selectedIndex = i
         })
       })
     }
     .padding(12)
     .backgroundColor('#F6F7F9')
     .height('100%')
   }
 }

这里有个“很像人写的”细节

很多人会把 “选中态” 用 @State 手动改背景色。 但更好的做法是:选中逻辑仍然由 @State,而视觉表现交给 stateStyles 去做“规范化”。


8. stateStyles 和普通链式样式谁优先?

我的经验结论:状态样式触发时,stateStyles 会覆盖你平时链式写的同类属性。 所以建议你这样写:

  • 普通链式:写“结构性固定的”(比如宽高、padding)
  • stateStyles:写“会因状态变化的”(比如背景、边框、透明度、字体颜色)

这样不会互相打架。


9. 如何把它写得更工程化(不然写多了也会乱)

当页面多了,你会遇到第二个问题:

stateStyles 写在页面里,还是会到处复制……

这时候我推荐你两条路:

路线 A:配合 @Styles 抽离“状态样式片段”

你把 normal/pressed/focused 的样式写成可复用片段,然后在多个组件里引用。

路线 B:配合 @Extend 做成“组件能力”

比如给 Button 做一个 .primaryState(),内部统一 stateStyles。 这样页面里写 UI 会很干净(我个人更喜欢这个)。

你前面已经在学 @Extend,这个组合拳其实很顺: @Extend 管入口,stateStyles 管状态映射表。


10. 我踩过的坑(你避开就能省很多时间)

  1. 不是所有组件都有所有状态 写了 focused 不生效,别急着怀疑自己,先确认这个组件是不是可获焦的。
  2. 不要把所有属性都塞进每个状态里 维护会非常痛苦。尽量“normal 写全量,其他状态写差异”。
  3. 获焦态在多设备上非常关键 手机触控你可能觉得 focused 不重要,但 TV/车机/键盘场景,没有 focused 体验会很差。
  4. 禁用态别只改颜色 最好再配合 .enabled(false) 或者业务上禁止点击,否则“看着像禁用,实际还能点”更糟糕。

11. 一句话总结(我的真实感受)

stateStyles 真正解决的不是“怎么换颜色”,而是:

把交互状态的视觉规则从业务逻辑里剥离出来,让 UI 写起来更像在搭积木,而不是在写一堆状态机。