在 Flutter 开发中,TabBar 字体缩放动画是常见的交互效果,但如果实现不当,会出现明显的抖动现象,尤其是在字体大小变化时,行高也会发生微小的变化,甚至可能导致下方的内容上下跳动,体验非常不流畅。
本文总结了这一问题的根本原因,并提供了几种优化方案。
问题分析
问题的核心原因有两点:
-
字体缩放导致矢量绘制抖动
Flutter 中 Text 渲染是矢量绘制,每次 fontSize 改变时都会重新计算文字像素点位置,而逻辑像素无法完全匹配物理像素,从而导致抖动。相关讨论可以参考:
-
动画实现方式
很多实现通过直接改变
fontSize
来做缩放动画,这种方式每帧都会重新计算文字布局,跳跃感明显。而使用Transform.scale
做缩放,则可以保持逻辑布局不变,通过位图缩放实现平滑动画,更接近 CSS 中transform: scale
的效果。
优化方案
方案一:Transform.scale + FilterQuality
可以将文字放在 Transform.scale
中进行缩放,并通过 filterQuality
控制渲染质量:
less
Transform.scale(
scale: scale,
filterQuality: FilterQuality.high, // 或 medium
child: Text(
'Tab名称',
style: const TextStyle(
color: Colors.black,
fontSize: 16,
height: 25 / 16,
),
),
);
优点:动画平滑、不卡顿
缺点:如果缩放比例过大,位图会出现模糊。可以考虑自己使用
CustomPaint
缓存高分辨率位图。
方案二:升级 Flutter 版本
Flutter 3.32+ 对 Transform.scale 渲染优化较好,动画效果更加丝滑,推荐在可能的情况下升级版本。
方案三:避免 FontWeight 动画
FontWeight 并非连续值,直接在动画中插值并不能实现自然过渡。实践中,大型 App(如 B 站漫画)通常只在切换 Tab 时直接切换 fontWeight,而不是做动画过渡。
示例代码
结合上述优化,可以实现如下效果:
ini
AnimatedBuilder(
animation: tabController.animation,
builder: (context, child) {
final tabIndex = tabs.indexOf(tab);
final animationValue = tabController.animation.value;
double scale = 1.0;
if ((animationValue - tabIndex).abs() <= 1.0) {
scale = 1.0 + 0.3 * (1.0 - (animationValue - tabIndex).abs());
}
return Transform.scale(
scale: scale,
filterQuality: FilterQuality.high,
child: Text(
tab.displayName,
style: const TextStyle(
color: Colors.black,
fontSize: 16,
height: 25 / 16,
),
),
);
},
);
总结
- 问题根源:字体缩放导致矢量重绘 + fontSize 插值产生布局跳动
- 优化思路 :使用
Transform.scale
代替 fontSize 动画,必要时开启filterQuality
- 注意点:避免 FontWeight 动画,直接切换即可
通过这些优化,TabBar 动画可以实现丝滑、平稳的效果,同时避免下方主体内容的抖动。