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:按钮的按下态 / 获焦态(最常用,没有之一)
按钮一般至少要做两件事:
pressed时颜色变深(给用户反馈)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. 我踩过的坑(你避开就能省很多时间)
- 不是所有组件都有所有状态 写了
focused不生效,别急着怀疑自己,先确认这个组件是不是可获焦的。 - 不要把所有属性都塞进每个状态里 维护会非常痛苦。尽量“normal 写全量,其他状态写差异”。
- 获焦态在多设备上非常关键 手机触控你可能觉得 focused 不重要,但 TV/车机/键盘场景,没有 focused 体验会很差。
- 禁用态别只改颜色 最好再配合
.enabled(false)或者业务上禁止点击,否则“看着像禁用,实际还能点”更糟糕。
11. 一句话总结(我的真实感受)
stateStyles 真正解决的不是“怎么换颜色”,而是:
把交互状态的视觉规则从业务逻辑里剥离出来,让 UI 写起来更像在搭积木,而不是在写一堆状态机。