The AnimatedBuilder
does the same job a AnimatedWidget
does but in a different way. It has a animation
property that takes a Listenable
object. In this case, you can assign AnimationController
or an Animation<T>
object. It takes another property builder
, which is a callback. The builder
method should return the UI that is the part of the animation. Whenever the provided Listenable
object changes its value, the AnimatedBuilder
widget calls its builder
callback to return a new UI.
Let's consider the same example. This time instead of AnimatedWidget
, we will use AnimatedBuilder
widget to achieve the same animation.
class _TestState extends State<Test> with SingleTickerProviderStateMixin{ AnimationController _controller; Animation<double> widthAnimation; Animation<double> opacityAnimation; Animation<Color> colorAnimation; @override initState(){ super.initState(); _controller = new AnimationController( duration: const Duration(milliseconds: 2000), vsync: this, ); widthAnimation = Tween<double>(begin: 0.0, end: 100).animate( CurvedAnimation( parent: _controller, curve: Interval(0.1, 0.4, curve: Curves.bounceIn) ) ); opacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _controller, curve: Interval(0.0, 1.0, curve: Curves.easeIn) ) ); colorAnimation = ColorTween(begin: Colors.yellow, end: Colors.red).animate( CurvedAnimation( parent: _controller, curve: Interval(0.4, 1.0, curve: Curves.easeOut) ) ); _controller.addListener((){ setState((){}); }); _controller.repeat(reverse: true); } @override dispose(){ _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context){ return BeautyBackground( child: Column( children: <Widget>[ BackButton(), Expanded( child: Center( child: SizedBox( width: (widthAnimation.value == null) ? 0.0 : widthAnimation.value, height: 100, child: Opacity( opacity : (opacityAnimation.value == null) ? 0.0 : opacityAnimation.value, child: DecoratedBox( decoration: BoxDecoration( color: (colorAnimation.value == null) ? Colors.red : colorAnimation.value, ), child: SizedBox( width: double.infinity, height: double.infinity ), ) ), ), ) ), ] ), ); } }
Now first thing we need to remove the following part:
_controller.addListener((){ setState((){}); });
Second, we need update the build
method. We should take the animation part within the AnimatedBuilder
widget. Here is the build method:
@override Widget build(BuildContext context){ return BeautyBackground( child: Column( children: <Widget>[ BackButton(), Expanded( child: Center( child: AnimatedBuilder( animation: _controller, builder: (BuildContext context, Widget child){ return SizedBox( width: (widthAnimation.value == null) ? 0.0 : widthAnimation.value, height: 100, child: Opacity( opacity : (opacityAnimation.value == null) ? 0.0 : opacityAnimation.value, child: DecoratedBox( decoration: BoxDecoration( color: (colorAnimation.value == null) ? Colors.red : colorAnimation.value, ), child: SizedBox( width: double.infinity, height: double.infinity ), ) ), ); }, ), ) ), ] ), ); }
The animation
property of the AnimatedWidget
is assigned to the _controller
. So whenever the controller changes its value, the widget calls its builder
method. This is done automatically by the widget. The builder
callback returns the new UI based on your current animations value. So your main build method which is in _TestState
class doesn't get rebuilt with the setState()
when controller changes its value. This is very efficient when your main build method is very large and you want only the animation part of the widget tree to be rebuilt.
The builder callback has two parameter, the first parameter is context
and second parameter is child
. We haven't used the child
parameter yet. Now we will see how you can pass a child
argument to the widget.
If some part of the widget tree that is returned from the builder
callback doesn't rely on the animation values, you can build it only once and reuse its reference. In this case you should pass a child
parameter which takes a Widget. This widget is passed to the builder
method. The builder
method uses that child
to insert it in the widget tree and then return it. This way you will be able to avoid unncessary rebuilding a specific part of your animation widget that doesn't change with the animation values.
In our above example, the last SizedBox
doesn't rely on the animation values. So we can move this part to the child
parameter.
@override Widget build(BuildContext context){ return BeautyBackground( child: Column( children: <Widget>[ BackButton(), Expanded( child: Center( child: AnimatedBuilder( animation: _controller, child: SizedBox( width: double.infinity, height: double.infinity ), builder: (BuildContext context, Widget child){ return SizedBox( width: (widthAnimation.value == null) ? 0.0 : widthAnimation.value, height: 100, child: Opacity( opacity : (opacityAnimation.value == null) ? 0.0 : opacityAnimation.value, child: DecoratedBox( decoration: BoxDecoration( color: (colorAnimation.value == null) ? Colors.red : colorAnimation.value, ), child: child, ) ), ); }, ), ) ), ] ), ); }
Using this pre-built child is entirely optional, but can improve performance significantly in some cases and is therefore a good practice.
So, this is all about AnimatedBuilder
widget. It's usage purpose is same as AnimatedWidget
, which is avoiding unncessary rebuilding of the whole widget tree. But the way they are used, are different.
Even though their key purpose of usage is same, there is a difference where they suits best.
The AnimatedBuilder
is helpful when you want to rebuild the animation part at once. In the above example, the width, opacity and the color all property updates together with a single call of the builder
callback. That means you cannot seperate the animations out from each other if you apply the multiple animation on a single widget. You must have to rebuild the whole animated widget at once. In other words the AnimatedBuilder
cannot be nested.
On the other hand AnimatedWidget
allows you to define a single animation per class, you cannot use multiple animation instance values. One class will allow only one listenable object. You can nest these subclasses to create the same effect, but each class will update its animation seperately. That means with AnimatedWidget
, animated property is updated seperately in seperated class.
Use either of them where they suits best.