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 我的选择建议
做项目时,我一般这样选:
- 团队协作项目:优先用AnimatedWidget。因为封装成组件后,团队成员可以直接复用,减少重复代码。
- 个人快速开发:用AnimatedBuilder。省事,不用额外定义类。
- 性能敏感场景:用AnimatedBuilder的child参数。比如列表项动画,能省则省。
你想想看,其实这两个东西本质是一样的。AnimatedBuilder就是Flutter官方帮你写好的一个AnimatedWidget子类。你完全可以把AnimatedBuilder当成一个「偷懒工具」来用。
最后说一句:不管选哪个,记得在dispose里释放AnimationController。这个坑我踩过好几次,动画不释放,页面退出后还在跑,内存泄漏妥妥的。