第 4 章 卡片与容器:Card、ElevatedCard、OutlinedCard

卡片是 Material 3 里最常用的容器组件。说白了,它就是用来装内容的“盒子”。但 Material 3 给了你三种选择:Card、ElevatedCard、OutlinedCard。它们长得差不多,行为细节却差异很大。我最早接触时也傻傻分不清,后来踩过坑才明白——选错卡片,轻则界面丑,重则动画卡顿。

4.1 三种卡片的底层区别

咱们先看个表格,把核心差异列出来。这样你心里就有数了。

组件 阴影 (Elevation) 边框 (Outline) 默认交互 典型场景
Card 0 (无) 无 扁平,无提升感 列表项、平铺网格
ElevatedCard 1~8 (可调) 无 按下去阴影变小,模拟按下 需要突出层级,比如推荐卡片
OutlinedCard 0 有 (1px 描边) 聚焦时描边变粗 表单区域、设置面板

你看,Card 就是一张白纸,没有立体感。ElevatedCard 浮起来了,自带阴影。OutlinedCard 靠边框区分,更像一个“框”。我个人习惯:内容层级不复杂的时候,直接用 Card;需要强调视觉权重时上 ElevatedCard;如果界面里已经有太多阴影,我会改用 OutlinedCard 来保持干净。

4.2 交互状态与动画要点

卡片不是静态的。用户点击、触摸、键盘聚焦时,它都要有反馈。Material 3 内置了涟漪(Ripple)效果,但这还不够——很多产品会加 hover 缩放、聚焦框等高阶交互。

举个例子,ElevatedCard 的按压效果:手指按下时阴影从 4 降到 2,并且表面颜色略变深。在 Flutter 里,你可以用 InkWell 包裹卡片,再通过 AnimatedContainer 控制阴影变化。但要注意,千万别直接改 elevation 这种触发 layout 的属性——我以前就犯过这个错,导致 60fps 掉到 30fps。

// ❌ 错误:直接改 elevation 会导致大量重绘
AnimatedContainer(
  duration: Duration(milliseconds: 200),
  child: Card(
    elevation: _isPressed ? 2.0 : 6.0, // 每次变化都触发 layout
  ),
);

// ✅ 正确:用 Transform 配合阴影层,只触发合成层
Transform(
  transform: Matrix4.identity()..translate(0.0, _raised ? -4.0 : 0.0),
  child: Card(
    elevation: 4,
    child: ...
  ),
);

这里有个核心原则:动阴影不移动画,动合成层不重绘。我建议你花时间把 Flutter 的 CompositingBits 搞清楚——嗯,有点深,但值得。

4.3 加密动画中的卡片优化

你可能奇怪,加密动画跟卡片有什么关系?实际场景里,密码输入框、密钥展示、指纹动画这些都需要容器。我接过一个项目,用户每次输入密码时,卡片要做一个“加密闪烁”动画——背景色从白变绿再变回来。

如果用 AnimatedContainer 直接改颜色,整张卡片会触发 paint。更好的办法是用 ClipRRect 或 ShaderMask 只改子区域。再有,卡片里面的文字、图标应该用 RepaintBoundary 隔离,避免跟着重绘。

优化技巧

  • 给卡片套一层 RepaintBoundary,隔离子组件。
  • 用 AnimatedContainer 时设置 duration 并减少变化属性数量。
  • 使用 willChange: Transform 或 opacity 提前告知引擎。
  • 动画中尽量只改 opacity 和 transform,避免改尺寸或阴影。

4.4 实战:三种卡片的动画效果对比

我准备了一张 SVG 图,把三者的典型交互画出来。注意看不同卡片在 hover、press、focus 下的表现差异。

三种卡片交互状态对比 Card Elevation: 0 Hover: 背景微亮 Press: 涟漪动画 Focus: 无描边变化 ElevatedCard Elevation: 4 Hover: 阴影增大 Press: 阴影减小 Focus: 边框高亮 OutlinedCard Elevation: 0 Hover: 描边变粗 Press: 涟漪+描边 Focus: 描边变亮 动画优化要点总结 优先使用 Transform 避免 layout 重排 RepaintBoundary 隔离子组件重绘 willChange 属性 提前提示引擎 图:三种卡片的默认状态与交互反馈,以及对应的动画优化策略

4.5 避坑指南:我曾经踩过的坑

讲几个我亲身经历的教训。

坑1:把 Elevation 当作动画变量

我曾经想让一个 ElevatedCard 在点击时“沉下去”,直接用了 AnimatedContainer(duration: 150ms),每次改变 elevation 属性。结果卡片每次跳变都导致整个列表重排,帧率直接跌到 20。后来改成用 Transform.translate 模拟按压下沉,性能立马回满。

坑2:OutlinedCard 聚焦动画导致闪烁

为 OutlinedCard 加聚焦边框动画时,直接改了 outlineWidth 属性,没使用合成层。导致每次聚焦都触发一层额外的 paint。解决办法:把边框画在 CustomPaint 里,或者用 AnimatedContainer 时设置 duration: 0ms 加显式补间。

核心原则

无论哪种卡片,动画优化的目标只有一个:把变化限制在合成层。避免修改会触发 layout 或 paint 的属性(比如 width、height、elevation、padding)。多用 opacity、transform、clip 这些轻量属性。

4.6 小结

卡片虽然简单,但选错了影响不小。记住几条:

  • 不需要层级 → Card
  • 需要突出浮起 → ElevatedCard
  • 需要边框区分 → OutlinedCard
  • 动画只动合成层,不做重排擦除

嗯,下一轮我们聊“表单与输入组件”的时候,你会发现卡片经常跟它们搭配使用。先把卡片的底子打好,后面才能搭出漂亮的交互效果。