Why use TweenAnimationBuilder? Suppose you want to create a basic animation: one that won’t repeat forever, just a widget or widget tree. Flutter has a convention for its implicit animation widget AnimatedFoo, where Foo is the name of the animation property. Don’t believe me? Here is an example of a built-in, implicit animation control:

AnimatedContainer, ‘ ‘AnimatedCrossFade,’ ‘AnimatedDefaultTextStyle,’ ‘AnimatedModalBarrier, AnimatedOpacity, AnimatedPadding AnimatedPhysicalModel, AnimatedPositioned AnimatedPositionedDirectional, AnimatedSwitcher.

This set of widgets is powerful, and you can use them to meet many requirements. AnimatedContainer even lets you set up gradient animations and rotate widgets without worrying about AnimationController!

However, if you need to create basic animations and you’re not looking for built-in implicit animations, you can still use them

TweenAnimationBuilder! To create the animation.

basis

To use TweenAnimationBuilder, use the duration parameter to set the length of time I want the animation to be used, and use… The Tween parameter sets the range of values to set for the animation. As the name implies, a Tween object allows you to specify the range of values to BE animated by BE Twain.

The last thing I need to specify is the Builder parameter, which returns what my animated widget looks like at a given time.

This constructor function takes the same type of argument as your Tween value, which basically tells Flutter what its current animation value is at a given moment.

/// This is a very brief description of using TweenAnimationBuilder. For tuning, see the rest of this article. /// Also note that this example is for illustrative purposes only - implicit rotation /// animation can be done using AnimatedContainer. class SuperBasic extends StatelessWidget { @override Widget build(BuildContext context) { return Stack( children: <Widget>[ starsBackground, Center( child: TweenAnimationBuilder<double>( tween: Tween<double>(begin: 0, end: 2 * math.pi), duration: Duration(seconds: 2), builder: (BuildContext context, double angle, Widget child) { return Transform.rotate( angle: angle, child: Image.asset('assets/Earth.png'), ); }, ), ), ], ); }}Copy the code

In-depth TweenAnimationBuilder

The sample code above shows the minimum set of parameters TweenAnimationBuilder that must be used, but there is much more to this widget! For illustrative purposes, I created an application for a very common use case: an illustration of doppler effects in space. Ok, this is a silly use case, but you might want to apply a color filter to the image and animate it for changing colors… That’s exactly what we’re going to do in this case.

In the Doppler effect, as the star moves away from you in space, the light wave lengthens, moving the light toward the red end of the spectrum. The effect is so subtle you can’t see it with the naked eye, but astronomers use it to determine the speed of stars and galaxies relative to us.

For more details, consult your local astrophysicist.

In our application, we’ll make it more subtle. I have a nice star image, and to change its color, I’ll use the ColorFiltered widget.

I applied blend mode and told it to blend orange into the image to make it reddish.

ColorFiltered (Child: image. asset ('assets/sun.png'), colorFilter: colorfilter.mode (color, blendmode.modulate),)Copy the code

The next step… Animation!

There is no built-in widget to apply arbitrary color filters to the widget, but we can build our own widget TweenAnimationBuilder. To change the color over time, we want to change the color applied to the filter. That’s what we want to put into life. We put the ColorFiltered widget into the Builder function TweenAnimationBuilder

. As mentioned earlier, a Tween is just the range of values we want to set for the animation. In this case, we’ll use a ColorTween to animate between the white (which seems to have no filter) and orange. There you have it! A beautifully animated color filter containing 10 lines of code

TweenAnimationBuilder( tween: ColorTween( begin: Colors.white, end: Colors.red), duration: Duration(seconds: 2), builder: (_, Color color, __) { return ColorFiltered( child: Image.asset('assets/sun.png'), colorFilter: ColorFilter.mode(color, BlendMode.modulate), ); },)Copy the code

Depending on what you want to animate, your Tween can also specify the range between things other than colors or numbers. You can animate the widget’s position changes with a Tween object with Offset, or even the widget’s boundary changes! The point is that you have many choices.

The Tween is mutable, so if you know that you’re always going to animate between the same set of values, it’s best to Tween you as a static final variable in your class. This way, you don’t create a new object every time you rebuild

class SuperBasic extends StatelessWidget { static final colorTween = ColorTween(begin:Colors.white, end:Colors.red); @override Widget build(BuildContext context) { return Stack( children: <Widget>[ starsBackground, Center( child: TweenAnimationBuilder<double>( tween: colorTween, duration: Duration(seconds: 2), builder: (BuildContext context, double angle, Widget child) { return Transform.rotate( angle: angle, child: Image.asset('assets/Earth.png'), ); }, ), ), ], ); }}Copy the code

Dynamically modify the Tween value

The previous example demonstrates a very simple way to animate one value to another without using setState anything. But you can do more with TweenAnimationBuilder by dynamically modifying Tween values.

class OngoingAnimationByModifyingEndTweenValue extends StatefulWidget {
    @override  
    _OngoingAnimationState createState() => _OngoingAnimationState();
} class _OngoingAnimationState extends State<OngoingAnimationByModifyingEndTweenValue> {
    double _newValue = 0.4;  
    Color _newColor = Colors.white;  
    @override  Widget build(BuildContext context) {
        return Stack(
          children: <Widget>[
            starsBackground, 
            Column(
              children: <Widget>[
                Center(
                  child: TweenAnimationBuilder(
                    tween: ColorTween(begin: Colors.white, end: _newColor), 
                    duration: Duration(seconds: 2),
                    builder: (_, Color color, __) {
                      return ColorFiltered(
                        child: Image.asset('assets/sun.png'),
                        colorFilter: ColorFilter.mode(color, BlendMode.modulate),
                      );
                    },
                  ),
                ),
                Slider.adaptive(
、                  value: _newValue,
                    onChanged: (double value) {
                        setState(() {
                          _newValue = value;
                          _newColor = Color.lerp(Colors.white, Colors.red, value);
                        });
                 },
              ), 
            ],
         ),
     ],);
  }
}
Copy the code

I changed the code to include a Slider widget as well. I then declare a local variable called _newColor

The local variable takes the slider value and converts it to a color. _newColor is also used as the final value Tween in my. Now, the animation updates every time I drag the slider.

One thing to keep in mind is that TweenAnimationBuilder always moves from the current value to the new final value. This means that when I drag the slider, I see the color change relative to its previous color, instead of always animating from white in the first place. Just set me a new final value Tween, and I can reverse the animation or move to any point in between. The TweenAnimationBuilder always sets the animation smoothly between its current value and the new endpoint. As you can infer, this means that dynamically changing the starting position Tween is not effective.

// DON'T DO THIS! YOU WON'T SEE AN ANIMATION IF YOU JUST UPDATE THE START VALUE! class NopeNopeNope extends StatefulWidget { @override _NopeNopeNopeState createState() => _NopeNopeNopeState(); } class _NopeNopeNopeState extends State<NopeNopeNope> { double _newValue = .4; Color _newColor = Colors.white; @override Widget build(BuildContext context) { return Stack( children: <Widget>[ starsBackground, Column( children: <Widget>[ Center( child: TweenAnimationBuilder( tween: ColorTween(begin: _newColor, end: Colors.red), duration: Duration(seconds: 2), builder: (_, Color color, __) { return ColorFiltered( child: Image.asset('assets/sun.png'), colorFilter: ColorFilter.mode(color, BlendMode.modulate), ); }, ), ), Slider.adaptive( value: _newValue, onChanged: (double value) { setState(() { _newValue = value; _newColor = Color.lerp(Colors.white, Colors.red, value); }); }, ), ], ),]); }}Copy the code

OnEnd and child

I haven’t talked about some of the other parameters. The first is the curve, curve, which describes how we should convert between the start and end values in our Tween range. In the last article, we discussed how you can even create custom curves, but there are a lot of great predefined options.

The second is a callback that you can specify, so you can do something when the animation is complete. Perhaps you want to display another widget after this animation is over. You can also use this callback as a way to reverse the animation back and forth. I recommend that you think carefully before performing this operation. Callbacks make it less clear what kind of animation you are trying to perform because the value Settings are assigned by code. Because these values are discontinuous (jumping back to the beginning again), some kind of explicit animation is required if the animation is to be played repeatedly: a built-in explicit animation widget or extend AnimatedWidget.

class _BackAndForthState extends State<MyHomePage> { Color _newColor = Colors.red; @override Widget build(BuildContext context) { return Stack( children: <Widget>[ starsBackground, Center( child: TweenAnimationBuilder( tween: ColorTween( begin: Colors.white, end: _newColor), duration: Duration(seconds: 2), onEnd: () { setState(() { _newColor = _newColor == Colors.red ? Colors.white : Colors.red; }); }, builder: (_, Color color, __) { return ColorFiltered( child: Image.asset('assets/sun.png'), colorFilter: ColorFilter.mode(color, BlendMode.modulate), ); }, ), ) ], ); }}Copy the code

The last parameter we haven’t discussed yet is child. Setting subparameters is a potential performance optimization. Even if the color changes, the star image widget itself stays the same. As written so far, the image widget is rebuilt each time the Builder method is called. We can build the star image ahead of time by passing it as a child parameter. In this way, the only widget that Flutter knows it needs to reconstruct from frame to frame is the new color filter, not the star image itself. This example is so simple that there is really no discernible difference. However, if we were to animate a more complex component, we could imagine that performance tuning might become even more important.

class ChildParameter extends StatelessWidget { static final colorTween = ColorTween(begin: Colors.white, end: Colors.red); @override Widget build(BuildContext context) { return Stack( children: <Widget>[ starsBackground, Center( child: TweenAnimationBuilder<Color>( tween: colorTween, child: Image.asset('assets/sun.png'), duration: Duration(seconds: 2), builder: (_, Color color, Widget myChild) { return ColorFiltered( child: myChild, colorFilter: ColorFilter.mode(color, BlendMode.modulate), ); }, ), ), ], ); }}Copy the code

conclusion

That’s using TweenAnimationBuilder! Everything you need to know to write your own cool implicit animations! Review that TweenAnimationBuilder is a good way to create a set and forget it implicit animation if you can’t find a widget with type AnimatedFoo built in. You can use TweenAnimationBuilder to complete simple animations without using StatefulWidget. You can change the final value Tween in to set it smoothly to the new value. You can also optimize performance by passing in the child or Tween in advance to set the static end result when appropriate.