第 3 章:组件基础——按钮、图标按钮、文本按钮

按钮这玩意儿,说简单也简单,说复杂……嗯,我见过不少项目在按钮上翻车。尤其是我们做加密动画优化,按钮就是动画的触发器。点一下按钮,加密动画开始;点一下,解密完成。这中间的反馈,全靠按钮的交互状态撑起来。

Material 3 把按钮分成了三类:实心按钮(Filled Button)、图标按钮(Icon Button) 和 文本按钮(Text Button)。三类按钮的背后,其实是一套统一的状态系统。你想想看,如果你对每个按钮都单独写一套动画逻辑,那维护起来得疯。我个人习惯是把状态抽象出来,再根据按钮类型微调视觉表现。

3.1 实心按钮(Filled Button)

实心按钮是主角。背景色最重,对比度最高,用户一眼就知道“点这里”。我做过一个加密钱包的登录页,就是靠一个实心按钮的点击反馈动画来判断网络延迟——动画卡了,说明网络有问题。

关键属性:

  • 状态:enabled / disabled / hover / focused / pressed
  • 动画属性:elevation 变化、background color 过渡、ripple 扩散
  • 加密场景:点击后立即触发加密动画,按钮自身 elevation 从 2 升到 6,模拟按下感
我的小技巧: 加密动画启动时,把按钮的 onPressed 暂时置空,避免用户重复点击。等动画结束再恢复。这比用 loading 状态更流畅。

3.2 图标按钮(Icon Button)

图标按钮很克制。它没有背景,只有一个图标 + 一个可选的圆形遮罩。在加密界面上,我常把“收藏/解密”功能做成图标按钮——用户习惯。不过要注意:图标按钮的点击热区其实很大,别被小图标骗了。

它的状态变化比实心按钮更微妙。hover 时出现半透明遮罩,pressed 时遮罩加深。我个人建议在加密动画的“撤销”或“查看详情”场景里用这个,别用在主要操作上——用户容易忽略。

一个我踩过的坑:早期版本图标按钮的 disabled 状态只改了颜色,不改变遮罩大小。结果用户以为按钮死掉了。Matrial 3 现在规定 disabled 图标按钮的透明度降到 0.38,同时取消任何遮罩动画——这才对。

注意: 图标按钮没有 elevation 动画。所以如果你想让图标按钮的点击有“弹起”效果,就得自己加一个 Transform.scale 动画。我试过,效果不错,但注意别跟 ripple 冲突。

3.3 文本按钮(Text Button)

文本按钮是最低调的。没有背景,没有阴影,只有一行文字。在加密动画里,我通常拿它做“取消”或“跳过动画”之类的辅助操作。用户点它的时候,预期是快速响应,而不是华丽的动画过渡。

为什么?因为文本按钮的 pressed 状态只是文字颜色变化 + ripple,非常轻。如果强行给它加一个 300ms 的渐变动画,用户会觉得卡——我测试过,延迟超过 100ms 用户就会皱眉。

你看,三种按钮的设计初衷完全不同。我们做优化的时候,不是给每个按钮套同样的动画模板,而是要理解它天生适合什么。

三类按钮对比一览

类型 视觉重量 常用场景 动画重点 加密场景建议
实心按钮 高 主要操作、提交 elevation + 背景过渡 触发加密动画主入口
图标按钮 中 工具栏、收藏、复制 遮罩 + 图标颜色 解密/查看/撤销
文本按钮 低 取消、跳过、次要操作 文字颜色 + ripple 跳过动画或重置

状态共享:背后的统一模式

三类按钮虽然长相不同,但 Material 3 给它们统一的状态管理。你只需要理解一套规则:

  1. 所有按钮都有 enabled / disabled / hover / focused / pressed 五种状态
  2. 每个状态都对应一套 颜色 + 高度 + 形状 的配置
  3. 动画过渡时长由 duration 控制,默认 200ms – 这是 Material 的运动原则

前阵子我重构一个加密 App,发现所有按钮的 pressed 状态动画用了 300ms,而 hover 用了 100ms。结果用户快速连点时,动画还没跑完就又触发新的状态,界面一顿乱闪。后来我把所有状态动画统一到 150ms,问题解决。这玩意儿,说白了就是状态机的节奏控制。

与加密动画的关联

现在想象一下:用户点了一个 实心按钮,触发了 AES 加密动画。动画持续 800ms,在这 800ms 内按钮应该保持 pressed 风格,并显示一个环形进度。动画结束后按钮恢复到正常状态,并且触发一个成功反馈(比如背景变绿 200ms 然后复原)。

这个流程里,按钮的状态变化就是加密动画的“节拍器”。如果按钮的状态跳变太早或太晚,用户会感觉动画脱节。所以我建议:

  • 加密进行中:按钮 disabled + 显示等待指示器
  • 加密完成:按钮 enabled + 短暂的颜色反馈
  • 加密失败:按钮 enabled + 红色抖动动画(注意抖动幅度别太大,3px 就够了)

核心思路: 按钮状态本质上是一种用户反馈信号。加密动画的每一步都该映射到按钮状态的变化上。别让按钮“静止”在动画中间。

本章知识体系

Material 3 按钮基础与加密动画关联 按钮状态系统 实心按钮 强调,主要操作 图标按钮 紧凑,工具栏常用 文本按钮 次要,低调取消 共享状态 enabled / disabled hover / focused / pressed 动画时长统一 150–200ms 触发加密动画 点击 → 状态锁定 → 动画 实心按钮是最常见的 加密操作触发器

看这张图就清楚了:三种按钮共享同一套底层状态机制,只是视觉侧重点不同。当你理解了状态系统,再去设计加密动画的触发、进行中和完成反馈,就会顺手很多。我的建议是:先统一管理按钮状态的变化时长,再针对不同类型微调颜色和 elevation。

最后提一句: 如果你用 Flutter,推荐用 MaterialStateProperty.resolveWith 来统一配置状态。不要把每个状态的颜色写死,而是写成一个函数——这样后期调动画曲线时,改一处就行。