Flutter Explicit Animations

Explicit animations allow you to build custom animations using the AnimatedBuilder widget. AnimatedBuilder lets you create an animation as part of a build method for another widget.

Creating an explicit animation

To create an explicit animation, you need to create an AnimationController to control the animation. The controller renders the animation and holds the animation duration, the Tween, and the direction of the animation such as forward or reverse. The controller requires a TickerProvider that is configured using a vsync argument on the constructor.

Explicit animation widgets 1. Import the appropriate Flutter package.
2. Declare your StatefulWidget class.
3. Declare the State class with the appropriate ticker: SingleTickerProviderStateMixin or TickerProviderStateMixin.
4. Declare an instance of Animation<T> and AnimationController.
5. Define optional and required parameters such as vsync, duration, and tween.
6. Specify the animation such as CurvedAnimation.
7. Add the appropriate action in the listener callback such as setState.
8. Start the animation.
9. Define the BuildContext to return the animation.
10. Stop and dispose of the animation.

Once you’ve created a controller, you can use it to build other animations. For example, you can create a ForwardAnimation then modify some of the parameters to create a ReverseAnimation that mirrors the original animation but runs in reverse. Similarly, you can create a CurvedAnimation whose value is adjusted by a curve and then reverse it.

Flutter with and without an explicit animation

Two code samples are shown below to highlight the difference between a Flutter app with an animation and a Flutter app without an animation.

Flutter logo without an animation

The following application draws the Flutter logo but it doesn’t include any way to control an animation. It doesn’t include an AnimationController, or vsync, ticker, or listener.

// Simple Flutter app without an animated logo

import 'package:flutter/material.dart';

class LogoApp extends StatefulWidget {
  _LogoAppState createState() => _LogoAppState();
}

class _LogoAppState extends State<LogoApp> {
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        margin: EdgeInsets.symmetric(vertical: 10.0),
        height: 300.0,
        width: 300.0,
        child: FlutterLogo(),
      ),
    );
  }
}

void main() {
  runApp(LogoApp());
}

Flutter logo with an explicit animation

The following application animates the Flutter logo.

The widget displays an animated Flutter logo that grows from nothing to full size.

To render the animation with an Animation<T> object, store the Animation object as a member of your widget, then use its changing value to draw the animation.

  • When you define an AnimationController, you must pass in a vsync object. The presence of vsync prevents offscreen animations from consuming unnecessary resources. You can use your stateful object as the vsync by adding SingleTickerProviderStateMixin to the class definition.
  • You must specify a ticker, tween, and listener.
  • The addListener() function calls setState(), so every time the animation generates a new value, the current frame is marked dirty, which forces a rebuild.
  • During the build, the container changes size because its height and width now use animation.value instead of a hardcoded value.
  • Dispose of the controller when the animation is finished to prevent memory leaks.

The changes to the non-animated example are highlighted.

```Dart
// Flutter app with an animated logo
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
class LogoApp extends StatefulWidget {
  _LogoAppState createState() =>  _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  Animation<double> animation;
  AnimationController controller;
  initState() {
    super.initState();
    controller = AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    animation = Tween(begin: 0.0, end: 300.0).animate(controller)
      ..addListener(() {
        setState(() {
// the state that has changed here is the animation object’s value
        });
      });
    controller.forward();
  }
  Widget build(BuildContext context) {
    return Center(
        child: Container(
        margin: EdgeInsets.symmetric(vertical: 10.0),
    height: animation.value,  
    width: animation.value,  
    child: FlutterLogo(),
    ),
    );
  }
  dispose() {
    controller.dispose();
    super.dispose();  
  }
}
void main() {
  runApp(LogoApp());
}
```

Simplifying with AnimatedWidget

To simplify an explicit animation, use the AnimatedWidget class instead of using addListener() and setState.

This example shows how AnimatedWidget is used to produce the same animation as the animation shown above in the Flutter logo with an explicit animation section above. The app displays an animated Flutter logo that grows from nothing to full size.

The AnimatedWidget class allows you to separate the widget code from the animation code in the setState() call. AnimatedWidget doesn’t need to maintain a State object to hold the animation.

In the refactored example below, LogoApp now derives from AnimatedWidget instead of StatefulWidget. AnimatedWidget uses the current value of the animation when drawing itself. The LogoApp still manages the AnimationController and the Tween.

The LogoApp passes the Animation object to the base class and uses animation.value to set the height and width of the container.

```Dart
// Simple Flutter animation using AnimatedWidget to create a separate animation widget

import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';

class AnimatedLogo extends AnimatedWidget {
  AnimatedLogo({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);

  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;
    return Center(
      child: Container(
        color: Colors.white,
        margin: EdgeInsets.symmetric(vertical: 10.0),
        height: animation.value,
        width: animation.value,
        child: FlutterLogo(),
      ),
    );
  }
}

class LogoApp extends StatefulWidget {
  _LogoAppState createState() => _LogoAppState();
}

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;

  initState() {
    super.initState();
    controller = AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    animation = Tween(begin: 0.0, end: 300.0).animate(controller);
    controller.forward();
  }

  Widget build(BuildContext context) {
    return AnimatedLogo(animation: animation);
  }

  dispose() {
    controller.dispose();
    super.dispose();
  }
}

void main() {
  runApp(LogoApp());
}

```

Monitoring the progress of an animation

It’s often helpful to know when an animation changes state, such as starting, stopping, or reversing direction.

Use addStatusListener for notifications of state changes.

```Dart
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;

  initState() {
    super.initState();
    controller = AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    animation = Tween(begin: 0.0, end: 300.0).animate(controller)
      ..addStatusListener((state) => print("$state"));
    controller.forward();
  }
  //...
}

```

The result might show the following status.

```Dart
AnimationStatus.forward
AnimationStatus.completed
```

To reverse the animation at the beginning or end (and create a “breathing” effect), add animation.addStatusListener.

```Dart
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;

  initState() {
    super.initState();
    controller = AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    animation = Tween(begin: 0.0, end: 300.0).animate(controller);

    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        controller.forward();
      }
    });
    controller.forward();
  }
  //...
}

```

Refactoring with AnimatedBuilder

You can refactor or restructure your animation code to improve code readability and reduce complexity. Refactoring also improves the source-code maintainability and creates a more expressive internal architecture or object model to improve extensibility.

By separating the responsibilities, or refactoring the code into different classes, you can separate the animation code so that if you change the animation, you won’t need to change the widget that renders the animated object.

To refactor animation code, restructure your code into the following sections:

  1. Render the image.
  2. Define the animation object.
  3. Render the animation.

In this section, we’ll refactor the animation code described in the sections above.

Render the image

Rendering the image in this example is straightforward—you create the object that is used as a parameter by AnimatedBuilder later when you define the Animation object.

```Dart
// Render the image (logo)

import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';

class LogoWidget extends StatelessWidget {
  // Leave out the height and width so it fills the animating parent
  build(BuildContext context) {
    return Container(
        margin: EdgeInsets.symmetric(vertical: 10.0),
        child: FlutterLogo());
  }
}
```

Define the animation object

When you define the animation object widget, it’s stateless and holds the set of final variables that are used to define the animation. The build() method creates and returns the AnimatedBuilder which takes an anonymous builder method and the rendered image object as parameters. The anonymous build() method creates a Container of the appropriate size to force the image to shrink to fit.

Similar to AnimatedWidget, you don’t need to call addListener() because AnimatedBuilder automatically listens to notifications from the Animation object.

Notice that it looks like the child is specified twice. The outer reference of child is passed to AnimatedBuilder which passes it to the anonymous closure—which then users that object as its child. The result is that the AnimatedBuilder is inserted between the two widgets in the render tree.

```Dart
// Define the Animation object

class GrowTransition extends StatelessWidget {
  GrowTransition({this.child, this.animation});

  final Widget child;
  final Animation<double> animation;

  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
          animation: animation,
          builder: (BuildContext context, Widget child) {
            return Container(
                height: animation.value, width: animation.value, child: child);
          },
          child: child),
    );
  }
}
```

Render the animation

The code to render the animation looks very similar to the code shown above in the Flutter logo with an explicit animation section.

The initState() method creates an AnimationController and specifies the duration, vsync, type of animation (CurvedAnimation), and Tween then binds them with animate(). The build() method returns a GrowTransition object with a LogoWidget as a child and an animation object to drive the transition.

```Dart
// Render the animation

class LogoApp extends StatefulWidget {
  _LogoAppState createState() => _LogoAppState();
}

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  Animation animation;
  AnimationController controller;

  initState() {
    super.initState();
    controller = AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    final CurvedAnimation curve =
        CurvedAnimation(parent: controller, curve: Curves.easeIn);
    animation = Tween(begin: 0.0, end: 300.0).animate(curve);
    controller.forward();
  }

  Widget build(BuildContext context) {
    return GrowTransition(child: LogoWidget(), animation: animation);
  }

  dispose() {
    controller.dispose();
    super.dispose();
  }
}

void main() {
  runApp(LogoApp());
}

```

Putting it all together. You can find the source code for this example in Refactoring animation code.

Simultaneous animations

To create simultaneous animations, you can use multiple tweens on the same AnimationController—where each tween manages a different effect in the animation.

The sample code in this section shows how you can build on the example shown in Simplifying with AnimatedWidget to create a continuous and simultaneous animation.

The snippet below shows one AnimationController and two tweens: one tween manages the opacity and the another tween manages the size.

```Dart
final AnimationController controller =
    AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
final Animation<double> sizeAnimation =
    Tween(begin: 0.0, end: 300.0).animate(controller);
final Animation<double> opacityAnimation =
    Tween(begin: 0.1, end: 1.0).animate(controller);
```

You can get the size with sizeAnimation.value and the opacity with opacityAnimation.value, but the constructor for AnimatedWidget only takes a single Animation object. To work with this constraint, the example shows how to create separate Tween objects and explicitly calculate the values.

The LogoApp widget was changed to encapsulate its own Tween objects. Its build method calls the Tween .evaluate() function on the parent’s animation object to calculate the required size and opacity values. The code changes are highlighted.

```Dart
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';

class AnimatedLogo extends AnimatedWidget {
  // The Tweens are static because they don't change.
  static final _opacityTween = Tween<double>(begin: 0.1, end: 1.0);
  static final _sizeTween = Tween<double>(begin: 0.0, end: 300.0);

  AnimatedLogo({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);

  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;
    return Center(
      child: Opacity(
        opacity: _opacityTween.evaluate(animation),
        child: Container(
          color: Colors.white,
          margin: EdgeInsets.symmetric(vertical: 10.0),
          height: _sizeTween.evaluate(animation),
          width: _sizeTween.evaluate(animation),
          child: FlutterLogo(),
        ),
      ),
    );
  }
}

class LogoApp extends StatefulWidget {
  _LogoAppState createState() => _LogoAppState();
}

class _LogoAppState extends State<LogoApp> with TickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;

  initState() {
    super.initState();
    controller = AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);

    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        controller.forward();
      }
    });

    controller.forward();
  }

  Widget build(BuildContext context) {
    return AnimatedLogo(animation: animation);
  }

  dispose() {
    controller.dispose();
    super.dispose();
  }
}

void main() {
  runApp(LogoApp());
}

```

Explicit animation widget examples

The table below lists some of the Flutter widgets that use explicit animations in their implementations. Links to the source code are provided so that you can view the code as a reference for writing your own explicit animations.

Explicit widget Description
BottomSheet This widget is a material design bottom sheet. There are two kinds of bottom sheets in material design:
Persistent—A persistent bottom sheet shows information that supplements the primary content of the app. A persistent bottom sheet remains visible even when the user interacts with other parts of the app.
Modal—A modal bottom sheet is an alternative to a menu or a dialog and prevents the user from interacting with the rest of the app.
Flutter library: material
ExpansionTile This widget is a single-line ListTile with a trailing button that expands or collapses the tile to reveal or hide the children.
Flutter library: material
PopupMenuButton This widget displays a menu when pressed and calls onSelected when the menu is dismissed because an item was selected. The value passed to onSelected is the value of the selected menu item.
Flutter library: material
ProgressIndicator This widget is a base class for Material Design progress indicators.
Flutter library: material
RefreshIndicator This widget supports the Material swipe to refresh idiom.
Flutter library: material
Scaffold This widget implements the basic Material Design visual layout structure and provides APIs for showing drawers, snack bars, and bottom sheets.
Flutter library: material
SnackBar This widget is a lightweight message with an optional action which briefly displays at the bottom of the screen. To display a snack bar, call Scaffold.of(context).showSnackBar(), passing an instance of SnackBar that describes the message.
Flutter library: material
TabBar This widget is a Material Design widget that displays a horizontal row of tabs.
Flutter library: material
TextField This widget is a Material Design text field that lets a user enter text, either with the hardware keyboard or with an onscreen keyboard.
Flutter library: material
ExpansionTile This widget is a single-line ListTile with with a trailing button that expands or collapses the tile to reveal or hide the children.
Flutter library: material