// Tutorial //

Basic Animations in Flutter

Published on September 25, 2019
Default avatar
By Joshua Hall
Developer and author at DigitalOcean.
Basic Animations in Flutter

While Flutter has an enormous amount of amazing packages for creating animation effects for your apps, there are also built-in methods for manually creating more fine-tuned animations.

Prerequisites

For our cross-screen animations I’ll be assuming that you already know how to create basic routes, just to keep things brief. You can review the docs here if you’re not comfortable with that just yet.

Linear Animations

The main three parts of our animations are the ticker to control our time, the controller to register our parameters like our duration, and then the values we want changed. Before our widget is rendered, in our initState, we can set our controller to its parameters, set its direction, and add a listener to reset our widgets state with every change.

By default a controller will move change from 0 to 1 in the time we set for our duration, we can print our controller.value in our listener to watch this happen. We can change our default start and end values by setting the upperBound or lowerBound properties.

main.dart
class MyHomePage extends StatefulWidget {
  
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
  AnimationController controller;

  void initState() {
    super.initState();
    controller = AnimationController(
      duration: Duration(seconds: 1),
      vsync: this); // Links this controller to this widget so it won't run if the parent widget isn't rendered, to save on resources.

    controller.forward();
    controller.addListener(() => setState(() {}));
  }
}

To use our animation we just need to set whatever we want, like opacity, to controller.value.


  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Opacity(
          opacity: controller.value,
          child: Container(width: 50, height: 50, color: Colors.red),
        ),
      ),
    );
  }

Curved Animations

Instead of boring linear animations we can use different curved variations to control exactly how our controller changes. To do this we can user CurvedAnimation to create a kind of wrapper over our original controller. This wrapper will take its parent, our controller, and the curve we want to apply. When using a curved animation we can’t have lower and upper bounds besides 0 and 1, so when we apply it we can just multiply its value. There’s a really extensive list of options over in the docs.

Another useful method on controller is addStatusListener, which will allow us to tap into its lifecycle. Our controller triggers a completed or dismissed event whenever it’s value has reached its upper or lower bound. We can use this with its forward and reverse methods to loop our animation infinitely.

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation animation;

  void initState() {
    super.initState();
    controller = AnimationController(
        duration: Duration(seconds: 1), 
        vsync: this);
    animation = CurvedAnimation(parent: controller, curve: Curves.slowMiddle);

    controller.forward();
    animation.addListener(() => setState(() {}));

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          margin: EdgeInsets.only(bottom: animation.value * 400),
          width: 50,
          height: 50,
          color: Colors.red),
      ),
    );
  }
}

Tweens

Instead of just working with number values, we can also work with ranges of other things, like colors, using tweens (short for between). Like with our curved animation, it will act as a wrapper for our controller or animation and we can set our color to its value.

main.dart
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation animation;
  Animation changeColor;

  void initState() {
    super.initState();
    controller =
        AnimationController(duration: Duration(seconds: 1), vsync: this);
    animation = CurvedAnimation(parent: controller, curve: Curves.slowMiddle);
    changeColor = ColorTween(begin: Colors.red, end: Colors.blue).animate(animation);
    controller.forward();
    animation.addListener(() => setState(() {}));

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          margin: EdgeInsets.only(bottom: animation.value * 400),
          width: 50,
          height: 50,
          color: changeColor.value),
      ),
    );
  }
}

Cross Screen

Another awesome technique is to use the Hero widget when we want a transition between widgets on different screens. We just need to wrap each in its own Hero widget with matching tags. The tags must be unique and there cannot be more than one on the screen at any time.

firstScreen
Widget build(BuildContext context) {
  return Scaffold(
    body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: <Widget>[
      SizedBox(height: 1),
      Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          Hero(tag: 'icon', child: Icon(Icons.add)),
        ]),
      Navbar()
  ]));
}
secondScreen
Widget build(BuildContext context) {
  return Scaffold(
    body: Column(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: <Widget>[
        SizedBox(height: 1),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
            SizedBox(width: 1),
            Hero(
              tag: 'icon',
              child: Icon(Icons.add, color: Colors.red, size: 75)),
          ]),
        Navbar()
      ]),
  );
}

Conclusion

With most frontend web technologies from the past, animations were left as an after-thought, but the flutter team did an amazing job at keeping animations in mind when developing this amazing framework. We really only scratched the surface of what Flutter is capable of in terms of animations.


Want to learn more? Join the DigitalOcean Community!

Join our DigitalOcean community of over a million developers for free! Get help and share knowledge in our Questions & Answers section, find tutorials and tools that will help you grow as a developer and scale your project or business, and subscribe to topics of interest.

Sign up
About the authors
Default avatar
Developer and author at DigitalOcean.

Still looking for an answer?

Was this helpful?
Leave a comment