第 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 下的表现差异。
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 - 动画只动合成层,不做重排擦除
嗯,下一轮我们聊“表单与输入组件”的时候,你会发现卡片经常跟它们搭配使用。先把卡片的底子打好,后面才能搭出漂亮的交互效果。