一开始它看起来只是“不对劲”
语音按钮的问题最初并不像一个严重 bug。
页面能打开,按钮能点,录音状态也能出现。但视觉上总觉得有些不对:按钮状态切换后,反馈不像预期那样稳定,某些瞬间像是背景、边界或状态样式没有对齐。
这种问题很容易被归类成 UI 细节:
- 是不是颜色不对;
- 是不是圆角没还原;
- 是不是 pressed 状态写错;
- 是不是某个背景 drawable 覆盖了;
- 是不是动画时机太快。
如果沿着这个方向修,很可能会不断微调样式,却始终没有抓到根因。
视觉问题有时是状态问题
后面继续追查时,我逐渐意识到:这个问题不是单纯的视觉还原偏差,而是状态边界不够清楚。
语音按钮不是一个静态控件。它至少承载这些状态:
idle
recording
recognizing
success
error
disabled
每个状态对应不同的文案、背景、动效、可点击性和后续操作。如果状态之间的切换没有被清楚建模,UI 表现就会变得像“视觉错觉”。
比如按钮刚从录音中切到识别中,如果旧状态的视觉残留没有被清理,用户看到的就不是一个明确状态,而是一段混合反馈。
问题藏在状态过渡,而不是某个样式值
这类问题最麻烦的地方在于,单独看每个状态都可能是对的。
idle 状态看起来正常。
recording 状态看起来也正常。
recognizing 状态单独触发时也不一定有问题。
真正的问题出现在过渡路径里:
- 从空闲到录音;
- 从录音到识别;
- 从识别成功到确认;
- 从识别失败回到空闲;
- 从录音中取消回到空闲;
- 从页面退出中断回到安全状态。
UI bug 经常不是“状态 A 错了”,而是“从状态 A 到状态 B 的路上漏了一步”。
用状态机重新描述按钮
修正这类问题时,我不想继续堆条件判断。
更稳的方式是把按钮当成一个小状态机,而不是一个有很多布尔值的 View。
Idle
-> Recording
-> Disabled
Recording
-> Recognizing
-> Idle
Recognizing
-> Success
-> Error
-> Idle
Success
-> Idle
Error
-> Idle
这样做的好处是,每一次状态切换都能被明确讨论:
- 进入状态时要设置哪些 UI;
- 离开状态时要清理哪些 UI;
- 当前状态是否允许点击;
- 是否允许重复触发;
- 异常发生时退回哪里。
当状态机清楚之后,视觉问题就不再靠猜。
AI 在这里适合做路径枚举
这次修复过程中,AI 最有用的地方不是直接给一个样式答案,而是帮我把状态路径枚举完整。
我可以让它围绕按钮行为追问:
- 有哪些入口会改变状态;
- 是否存在重复点击;
- 异步回调回来时页面是否还存在;
- 失败后是否恢复可点击;
- 成功后是否等待用户确认;
- 取消时是否回到一致的空闲态。
这些问题比“把颜色改浅一点”更接近根因。
对 UI 状态复杂的控件来说,AI 适合做路径穷举,人负责判断这些路径是否符合产品体验。
这次修复带来的提醒
表面上,这是一次语音按钮的视觉修复。
但真正沉淀下来的经验是:当一个 UI 问题反复调样式仍然不稳定时,应该暂停一下,检查它背后的状态模型。
尤其是这类控件:
- 会响应用户操作;
- 会等待异步结果;
- 会出现成功和失败;
- 会涉及权限或网络;
- 会在页面生命周期中被中断。
它们都不应该只被当成“按钮样式”。
UI 的稳定感来自状态的稳定。视觉错觉很多时候不是眼睛的问题,而是状态边界没有被工程化地表达出来。