Guide to Flutter implicit animations

Guide to Flutter implicit animations

Looking for an effective way to upgrade your app? Consider using a cool animation. Flutter renders everything on the screen relying on Skia graphics library, instead of using those pesky native views. Therefore, you can animate literally anything. In this tutorial, you will learn how to implement basic animations in Flutter.

 

Implicit animations

 

Flutter includes a series of widgets that are animated versions of existing widgets that you’ve probably already used in your app. For instance, Container -> AnimatedContainer, Positioned -> AnimatedPositioned,  Opacity -> AnimatedOpacity are widgets you will see in this tutorial. These widgets automatically animate changes to their properties. They extend ImplicitlyAnimatedWidget that’s why these animations, in general, are called Implicit animations.

 

 

AnimatedContainer in action (using hot reload)

 

Although implicit animated widgets are easy to use, they have some disadvantages. Most important – you don’t have full control of these animations, so it is not possible to implement repeating animation or listen to the animation state and react to the changes.

 

Practice

 

Let’s look a little closer at how you can use one of those widgets to implement animations in your app.

In this app, we have a typical structure: AppBar, home page and a drawer (side menu). For this sample we will implement drawer by ourselves, instead of using drawer property in Scaffold widget.

Here we create a basic app with MainPage and Container which is our own implementation of drawer.

 

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Home(),
    );
  }
}

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State {
  var isShownMenu = false;

  @override
  Widget build(BuildContext context) {
    final width = MediaQuery.of(context).size.width * 0.65;
    final height = MediaQuery.of(context).size.height;
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.blue,
        title: Text("Home"),
        leading: IconButton(
            icon: Icon(Icons.menu),
            onPressed: () {
              setState(() {
                isShownMenu = !isShownMenu;
              });
            }),
      ),
      body: Stack(
        children: [
          MainPage(),
          Positioned(
            width: width,
            height: height,
            left: isShownMenu ? 0 : -width,
            top: 0,
            child: Container(
              width: width,
              color: Colors.redAccent,
              height: double.infinity,
              child: ListView(
                  children: [
                    ListTile(title: Text("Home"),),
                    ListTile(title: Text("Settings")),
                    ListTile(title: Text("Logout"),)
                  ],
                ),
              ),
          ),
        ],
      ),
    );
  }
}

 

 

Drawer without animation

 

You can see AppBar here with IconButton for opening menu. Body of the Scaffold consist of Stack with MainPage and our drawer. Its visibility is controlled by setting negative left when hidden and zero left when visible. MainPage is a simple Container with Text widget, it is omitted for brevity. There are no animations for now and drawer opening looks a little clunky.

We can add a simple animation by replacing Positioned with AnimatedPositioned. Let’s check the code.

 

  @override
  Widget build(BuildContext context) {
    ………
      body: Stack(
        children: [
          MainPage(),
          _buildAnimatedMenu(width, height),
        ],
      ),
    );
  }

  Widget _buildAnimatedMenu(double width, double height) {
    return AnimatedPositioned(
        duration: Duration(milliseconds: 300),
        width: width,
        height: height,
        left: isShownMenu ? 0 : -width,
        top: 0,
        child: MainMenu(
          width: width,
        ));
  }

 

Same as AnimatedContainer, AnimatedPositioned widget will animate any changes to its properties and this is exactly what we need. By setting only one additional property – duration, which defines how long animation will run, we added animation to the drawer opening.

Usually, drawer menu should dim what is behind it. You can implement such behavior in many ways, but we will use AnimatedOpacity in this case.

 

  @override
  Widget build(BuildContext context) {
    ………
      body: Stack(
        children: [
          MainPage(),
          _buildBackgroundSkim(), // new code
          _buildAnimatedMenu(width, height),
        ],
      ),
    );
  }

  Widget _buildBackgroundSkim() {
    return AnimatedOpacity(
      duration: Duration(milliseconds: 300),
      opacity: isShownMenu ? 1 : 0,
      child: Container(
        color: Color.fromARGB(42, 0, 0, 0),
      ),
    );
  }

 

New function returns AnimatedOpacity widget with the same duration as our previous animation. Property opacity is set to 1 if menu is visible and to 0 if no. Child of this widget is a simple Container with transparent grey background.

 

 

Result of using AnimatedPositioned and

 AnimatedOpacity (background dimming)

 

Adding Curves

 

Animations in all animated widgets are constant by default, they are changing their values in a linear way. Changing the curves is as easy as setting another property in the constructor. Look at the code below.

 

Widget _buildAnimatedMenu(double width, double height) {
    return AnimatedPositioned(
        duration: Duration(milliseconds: 300),
        width: width,
        height: height,
        left: isShownMenu ? 0 : -width,
        top: 0,
        curve: isShownMenu ? Curves.bounceOut : Curves.linear, // new code
        child: MainMenu(
          width: width,
        ));
  }
}

 

Linear curve is set if the menu is shown, so next animation (dismissing the menu) will be linear, otherwise bounceOut is set. All possible predefined curves can be found here.

 

 

Conclusion

Using Implicit Animated Widgets is the most straightforward way to implement basic animations in Flutter. But keep in mind, that basic animation is the one which doesn’t repeat forever and it is only for one widget. If you want to implement custom animations check AnimatedWidget and AnimatedBuilder.