885  字
  4  分钟 
  Flutter 动画性能优化 
 0. 预期
- 目标帧预算:60 Hz ≈ 16 ms/帧;120 Hz ≈ 8 ms/帧(UI 线程 + 栅格线程各占一半)。用 DevTools 的 Performance/Timeline 看 UI/Raster 两条帧柱,定位卡顿尖峰。
- 调试开关:debugRepaintRainbowEnabled看重绘;PerformanceOverlay看两线程耗时。只在 debug 有效。
1. 控制重建范围(Widget 层,收益最大)
要点
- 将 setState局部化;多拆分小组件;尽量用const。
- AnimatedBuilder/- TweenAnimationBuilder利用- child参数,让不受动画影响的子树不随帧重建。
示例
class SpinningIcon extends StatelessWidget with TickerProviderStateMixin {  const SpinningIcon({super.key});
  @override  Widget build(BuildContext context) {    final controller = AnimationController(      vsync: this,      duration: const Duration(seconds: 2),    )..repeat();
    final rotation = CurvedAnimation(parent: controller, curve: Curves.linear);
    return AnimatedBuilder(      animation: rotation,      child: const Icon(Icons.autorenew, size: 24), // 静态子树,不随帧重建      builder: (context, child) => Transform.rotate(        angle: rotation.value * 6.2831853,        child: child,      ),    );  }}2. 减少重绘 & 避免昂贵效果(渲染层)
2.1 Opacity/剪裁/离屏层
- 少用 Opacity做动画;优先AnimatedOpacity/FadeTransition/FadeInImage。Opacity常触发离屏层(saveLayer),会贵。
- saveLayer、- BackdropFilter、- ShaderMask等都可能很贵;能不用就不用。
- 需要分割重绘时用 RepaintBoundary隔离易变子树,但别滥用(层次过多也有开销)。
示例:淡入图片用 FadeTransition 而非包一层 Opacity
class FadingImage extends StatefulWidget {  const FadingImage({super.key});  @override  State<FadingImage> createState() => _FadingImageState();}
class _FadingImageState extends State<FadingImage> with SingleTickerProviderStateMixin {  late final AnimationController _c = AnimationController(    vsync: this,    duration: const Duration(milliseconds: 350),  )..forward();
  @override  void dispose() { _c.dispose(); super.dispose(); }
  @override  Widget build(BuildContext context) {    return FadeTransition(      opacity: CurvedAnimation(parent: _c, curve: Curves.easeOut),      child: const RepaintBoundary(child: FlutterLogo(size: 80)),    );  }}2.2 布局 vs 变换
- 能用 Transform.translate/scale、SlideTransition的地方,尽量别用会反复触发布局的AnimatedPositioned/AnimatedContainer(尤其在复杂列表里)。依据:最佳实践强调控制build()成本、避免重复布局与内在尺寸遍历。
3. 资源与图片:把活干在对的地方
- 网络/大图:解码前降采样——给 Image.*传cacheWidth/cacheHeight(按 DPR 计算),可显著降内存/CPU。
- 滤镜采样:动画缩放/旋转中,必要时调 filterQuality(none/low/medium/high;默认 medium),在质量与开销之间取舍。
- 预取:提前 precacheImage(例如页面切换/首帧前,为 Hero/大图做预热)。
4. 列表/滚动中的动画(大场景的关键)
- 用 ListView.builder/SliverList的懒加载;必要时调整cacheExtent以平衡预渲染与内存。
- 滚动很快时延后昂贵任务(图片解码/渲染):Scrollable.recommendDeferredLoadingForContext(context);或使用ScrollAwareImageProvider。
- Tab/Page 场景保活:确需保留状态时用 AutomaticKeepAliveClientMixin,避免来回重建导致动画与数据抖动。
示例:列表中按需推迟加载图片
class PhotoTile extends StatelessWidget {  final String url;  const PhotoTile(this.url, {super.key});
  @override  Widget build(BuildContext context) {    final defer = Scrollable.recommendDeferredLoadingForContext(context);    return SizedBox(      height: 160,      child: defer          ? const Center(child: CircularProgressIndicator(strokeWidth: 2))          : Image.network(              url,              fit: BoxFit.cover,              // 解码目标尺寸(示意,结合 DPR 计算)              cacheWidth: (MediaQuery.of(context).size.width * MediaQuery.of(context).devicePixelRatio).toInt(),            ),    );  }}5. 暂停看不见的动画
- 离屏/隐藏时停表:用 TickerMode(enabled: false)或把不在当前 Tab 的动画子树禁用掉(要求控制器基于TickerProvider创建)。
TickerMode(  enabled: isCurrentPage, // 非当前页直接停表,省 CPU/GPU  child: yourAnimatedSubtree,)6. 非 UI 线程
- Impeller:Flutter 3.27 起,iOS 与 Android(API 29+) 默认启用;它在构建期预编译更少的着色器,显著降低首帧/早期的 shader jank。只有在你主动关闭 Impeller 或使用自定义 FragmentProgram时,才考虑 SkSL 预热这类补救。
- Isolate/compute:JSON 解析、图片处理、密集计算不要卡在主 isolate;用 compute()/Isolate后台做,避免动画掉帧。
// 将重 CPU 的解析放到后台 isolatefinal parsed = await compute(parseJson, rawJson);7. Checklist
- DevTools 定位:看 UI/Raster 哪条柱子超时;只在 profile/release 下评估。
- 限制 rebuild:拆组件、用 const、AnimatedBuilder.child。
- 限制 repaint:必要时 RepaintBoundary;少Opacity/BackdropFilter/saveLayer。
- 列表:builder懒加载、recommendDeferredLoadingForContext、按需cacheExtent。
- 图片:cacheWidth/Height降采样、按需filterQuality、precacheImage预热。
- 隐藏就停:TickerMode(false)。
- 引擎/线程:重活放 compute/Isolate。
 Flutter 动画性能优化 
  https://blog.lpkt.cn/posts/flutter-anim-opt/     
  