4、动画组件AnimatedWidget:AnimatedWidget原理、AnimatedBuilder、AnimatedWidget与AnimatedBuilder对比

4.1 为什么需要AnimatedWidget?

做Flutter动画时,你可能会遇到一个尴尬的场景:

你写了一个StatefulWidget,里面维护着Animation对象。每次动画值变化,都要手动调用setState()来刷新UI。代码写多了,你会发现大量重复的setState调用,看着就烦。

我刚开始做动画时,就踩过这个坑。一个页面里五六个动画,每个都要写addListener(() => setState(() {}))。后来代码维护起来,简直想骂人。

AnimatedWidget就是来解决这个问题的。它帮我们自动监听动画变化,自动刷新UI。说白了,就是把setState的脏活累活给包了。

4.2 AnimatedWidget原理

AnimatedWidget是一个抽象类,继承自StatefulWidget。它的核心机制很简单:

  • 接收一个Listenable对象(通常是Animation)
  • 内部自动添加监听器
  • 监听器触发时,自动调用setState

来看它的源码核心部分:

abstract class AnimatedWidget extends StatefulWidget {
  final Listenable listenable;

  const AnimatedWidget({Key? key, required this.listenable}) : super(key: key);

  @override
  State<AnimatedWidget> createState() => _AnimatedWidgetState();
}

class _AnimatedWidgetState extends State<AnimatedWidget> {
  @override
  void initState() {
    super.initState();
    widget.listenable.addListener(_handleChange);
  }

  void _handleChange() {
    setState(() {});
  }

  @override
  void dispose() {
    widget.listenable.removeListener(_handleChange);
    super.dispose();
  }
}

看到了吗?它就是在initState里加了监听,在dispose里移除。每次动画值变化,自动触发setState。你只需要继承它,重写build方法就行了。

核心要点:AnimatedWidget帮你管理了动画监听的生命周期。你不需要手动addListener和removeListener,它全包了。

4.3 实战:用AnimatedWidget实现渐隐渐显

我习惯把动画逻辑封装成独立的Widget,这样复用性特别好。举个例子:

class FadeInOutWidget extends AnimatedWidget {
  final Widget child;

  const FadeInOutWidget({
    Key? key,
    required Animation<double> animation,
    required this.child,
  }) : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Opacity(
      opacity: animation.value,
      child: child,
    );
  }
}

// 使用方式
class MyPage extends StatefulWidget {
  @override
  _MyPageState createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> with SingleTickerProviderStateMixin> {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 2),
    );
    _animation = Tween(begin: 0.0, end: 1.0).animate(_controller);
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return FadeInOutWidget(
      animation: _animation,
      child: Text('Hello Flutter'),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

嗯,这里要注意:AnimatedWidget的build方法里,需要通过listenable拿到Animation对象,再取value。这个步骤不能省。

4.4 AnimatedBuilder:更灵活的替代方案

有时候你不想专门写一个类继承AnimatedWidget。或者你需要在动画变化时,同时更新多个不相关的Widget。这时候AnimatedBuilder就派上用场了。

AnimatedBuilder本质上是对AnimatedWidget的封装。它接收一个builder回调,让你在回调里构建UI。我个人觉得,AnimatedBuilder更适合快速原型开发。

AnimatedBuilder(
  animation: _animation,
  builder: (context, child) {
    return Opacity(
      opacity: _animation.value,
      child: child,
    );
  },
  child: Text('Hello Flutter'),
)

看到那个child参数了吗?这是一个优化技巧。如果动画只影响外层容器,内层子Widget可以传进来,避免每次重建。我在项目中经常用这个优化,尤其是列表项动画时,效果很明显。

小技巧:AnimatedBuilder的child参数,用于传递不需要随动画变化的子Widget。这样可以减少Widget树的构建开销。

4.5 AnimatedWidget vs AnimatedBuilder 对比

很多初学者会纠结选哪个。我直接给你一个对比表,看完就明白了:

对比维度 AnimatedWidget AnimatedBuilder
使用方式 需要继承并重写build 直接传入builder回调
代码量 需要单独定义类,代码较多 内联使用,代码简洁
复用性 高,适合封装通用动画组件 低,适合一次性使用
性能优化 需要手动处理child缓存 内置child参数,方便优化
适用场景 项目中多次复用的动画组件 页面内一次性动画效果

说白了,选择标准就一条:

  • 如果你要封装一个可复用的动画组件(比如渐隐渐显、缩放动画),用AnimatedWidget
  • 如果你只是临时用一下,不想多写一个类,用AnimatedBuilder

避坑指南:我曾经在项目中过度使用AnimatedBuilder,一个页面里嵌套了七八个。结果动画卡顿严重。后来发现,每个AnimatedBuilder都会独立监听动画变化。如果多个动画共用同一个AnimationController,最好合并成一个AnimatedBuilder,或者用AnimatedWidget统一管理。

4.6 我的选择建议

做项目时,我一般这样选:

  1. 团队协作项目:优先用AnimatedWidget。因为封装成组件后,团队成员可以直接复用,减少重复代码。
  2. 个人快速开发:用AnimatedBuilder。省事,不用额外定义类。
  3. 性能敏感场景:用AnimatedBuilder的child参数。比如列表项动画,能省则省。

你想想看,其实这两个东西本质是一样的。AnimatedBuilder就是Flutter官方帮你写好的一个AnimatedWidget子类。你完全可以把AnimatedBuilder当成一个「偷懒工具」来用。

最后说一句:不管选哪个,记得在dispose里释放AnimationController。这个坑我踩过好几次,动画不释放,页面退出后还在跑,内存泄漏妥妥的。