Implicit animations

Welcome to the implicit animations codelab, where you learn how to use Flutter widgets that make it easy to create animations for a specific set of properties.

To get the most out of this codelab, you should have basic knowledge about:

This codelab covers the following material:

  • Using AnimatedOpacity to create a fade-in effect.
  • Using AnimatedContainer to animate transitions in size, color, and margin.
  • Overview of implicit animations and techniques for using them.

Estimated time to complete this codelab: 15-30 minutes.

What are implicit animations?

#

With Flutter's animation library, you can add motion and create visual effects for the widgets in your UI. One widget set in the library manages animations for you. These widgets are collectively referred to as implicit animations, or implicitly animated widgets, deriving their name from the ImplicitlyAnimatedWidget class that they implement. With implicit animations, you can animate a widget property by setting a target value; whenever that target value changes, the widget animates the property from the old value to the new one. In this way, implicit animations trade control for convenience—they manage animation effects so that you don't have to.

Example: Fade-in text effect

#

The following example shows how to add a fade-in effect to existing UI using an implicitly animated widget called AnimatedOpacity. The example begins with no animation code—it consists of a Material App home screen containing:

  • A photograph of an owl.
  • One Show details button that does nothing when clicked.
  • Description text of the owl in the photograph.

Fade-in (starter code)

#

To view the example, Click Run:

// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.

import 'package:flutter/material.dart';

const owlUrl =
    'https://raw.githubusercontent.com/flutter/website/main/src/content/assets/images/docs/owl.jpg';

class FadeInDemo extends StatefulWidget {
  const FadeInDemo({super.key});

  @override
  State<FadeInDemo> createState() => _FadeInDemoState();
}

class _FadeInDemoState extends State<FadeInDemo> {
  @override
  Widget build(BuildContext context) {
    return ListView(children: <Widget>[
      Image.network(owlUrl),
      TextButton(
        child: const Text(
          'Show Details',
          style: TextStyle(color: Colors.blueAccent),
        ),
        onPressed: () => {},
      ),
      const Column(
        children: [
          Text('Type: Owl'),
          Text('Age: 39'),
          Text('Employment: None'),
        ],
      )
    ]);
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: FadeInDemo(),
        ),
      ),
    );
  }
}

void main() {
  runApp(
    const MyApp(),
  );
}

Animate opacity with AnimatedOpacity widget

#

This section contains a list of steps you can use to add an implicit animation to the fade-in starter code. After the steps, you can also run the fade-in complete code with the changes already made. The steps outline how to use the AnimatedOpacity widget to add the following animation feature:

  • The owl's description text remains hidden until the user clicks Show details.
  • When the user clicks Show details, the owl's description text fades in.

1. Pick a widget property to animate

#

To create a fade-in effect, you can animate the opacity property using theAnimatedOpacity widget. Wrap the Column widget in an AnimatedOpacity widget:

{opacity1 → opacity2}/lib/main.dart RENAMED
@@ -26,12 +26,14 @@
26
26
  ),
27
27
  onPressed: () => {},
28
28
  ),
29
- const Column(
30
- children: [
31
- Text('Type: Owl'),
32
- Text('Age: 39'),
33
- Text('Employment: None'),
34
- ],
29
+ AnimatedOpacity(
30
+ child: const Column(
31
+ children: [
32
+ Text('Type: Owl'),
33
+ Text('Age: 39'),
34
+ Text('Employment: None'),
35
+ ],
36
+ ),
35
37
  )
36
38
  ]);
37
39
  }

2. Initialize a state variable for the animated property

#

To hide the text before the user clicks Show details, set the starting value for opacity to zero:

{opacity2 → opacity3}/lib/main.dart RENAMED
@@ -15,6 +15,8 @@
15
15
  }
16
16
  class _FadeInDemoState extends State<FadeInDemo> {
17
+ double opacity = 0;
18
+
17
19
  @override
18
20
  Widget build(BuildContext context) {
19
21
  return ListView(children: <Widget>[
@@ -27,6 +29,7 @@
27
29
  onPressed: () => {},
28
30
  ),
29
31
  AnimatedOpacity(
32
+ opacity: opacity,
30
33
  child: const Column(
31
34
  children: [
32
35
  Text('Type: Owl'),

3. Set the duration of the animation

#

In addition to an opacity parameter, AnimatedOpacity requires a duration to use for its animation. For this example, you can start with 2 seconds:

{opacity3 → opacity4}/lib/main.dart RENAMED
@@ -29,6 +29,7 @@
29
29
  onPressed: () => {},
30
30
  ),
31
31
  AnimatedOpacity(
32
+ duration: const Duration(seconds: 2),
32
33
  opacity: opacity,
33
34
  child: const Column(
34
35
  children: [

4. Set up a trigger for animation and choose an end value

#

Configure the animation to trigger when the user clicks Show details. To do this, change opacity state using the onPressed() handler for TextButton. To make the FadeInDemo widget become fully visible when the user clicks Show details, use the onPressed() handler to set opacity to 1:

{opacity4 → opacity5}/lib/main.dart RENAMED
@@ -26,7 +26,9 @@
26
26
  'Show Details',
27
27
  style: TextStyle(color: Colors.blueAccent),
28
28
  ),
29
- onPressed: () => {},
29
+ onPressed: () => setState(() {
30
+ opacity = 1;
31
+ }),
30
32
  ),
31
33
  AnimatedOpacity(
32
34
  duration: const Duration(seconds: 2),

Fade-in (complete)

#

Here's the example with the completed changes you've made. Run this example then click Show details to trigger the animation.

// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.

import 'package:flutter/material.dart';

const owlUrl =
    'https://raw.githubusercontent.com/flutter/website/main/src/content/assets/images/docs/owl.jpg';

class FadeInDemo extends StatefulWidget {
  const FadeInDemo({super.key});

  @override
  State<FadeInDemo> createState() => _FadeInDemoState();
}

class _FadeInDemoState extends State<FadeInDemo> {
  double opacity = 0;

  @override
  Widget build(BuildContext context) {
    return ListView(children: <Widget>[
      Image.network(owlUrl),
      TextButton(
        child: const Text(
          'Show Details',
          style: TextStyle(color: Colors.blueAccent),
        ),
        onPressed: () => setState(() {
          opacity = 1;
        }),
      ),
      AnimatedOpacity(
        duration: const Duration(seconds: 2),
        opacity: opacity,
        child: const Column(
          children: [
            Text('Type: Owl'),
            Text('Age: 39'),
            Text('Employment: None'),
          ],
        ),
      )
    ]);
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: FadeInDemo(),
        ),
      ),
    );
  }
}

void main() {
  runApp(
    const MyApp(),
  );
}

Putting it all together

#

The Fade-in text effect example demonstrates the following features of the AnimatedOpacity widget.

  • It listens for state changes to its opacity property.
  • When the opacity property changes, it animates the transition to the new value for opacity.
  • It requires a duration parameter to define how long the transition between the values should take.

Example: Shape-shifting effect

#

The following example shows how to use the AnimatedContainer widget to animate multiple properties (margin, borderRadius, and color) with different types (double and Color). The example begins with no animation code. It starts with a Material App home screen that contains:

  • A Container widget configured with a borderRadius, margin, and color. These properties are setup to be regenerated each time you run the example.
  • A Change button that does nothing when clicked.

Shape-shifting (starter code)

#

To start the example, click Run.

// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.

import 'dart:math';

import 'package:flutter/material.dart';

double randomBorderRadius() {
  return Random().nextDouble() * 64;
}

double randomMargin() {
  return Random().nextDouble() * 64;
}

Color randomColor() {
  return Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
}

class AnimatedContainerDemo extends StatefulWidget {
  const AnimatedContainerDemo({super.key});

  @override
  State<AnimatedContainerDemo> createState() => _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
  late Color color;
  late double borderRadius;
  late double margin;

  @override
  void initState() {
    super.initState();
    color = randomColor();
    borderRadius = randomBorderRadius();
    margin = randomMargin();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            SizedBox(
              width: 128,
              height: 128,
              child: Container(
                margin: EdgeInsets.all(margin),
                decoration: BoxDecoration(
                  color: color,
                  borderRadius: BorderRadius.circular(borderRadius),
                ),
              ),
            ),
            ElevatedButton(
              child: const Text('Change'),
              onPressed: () => {},
            ),
          ],
        ),
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: AnimatedContainerDemo(),
    );
  }
}

void main() {
  runApp(
    const MyApp(),
  );
}

Animate color, borderRadius, and margin with AnimatedContainer

#

This section contains a list of steps you can use to add an implicit animation to the shape-shifting starter code. After completing each step, you can also run the complete shape-shifting example with the changes already made.

The shape-shifting starter code assigns each property in the Container widget a random value. Associated functions generate the relevant values:

  • The randomColor() function generates a Color for the color property
  • The randomBorderRadius() function generates a double for the borderRadius property.
  • The randomMargin() function generates a double for the margin property.

The following steps use the AnimatedContainer widget to:

  • Transition to new values for color, borderRadius, and margin whenever the user clicks Change.
  • Animate the transition to the new values for color, borderRadius, and margin whenever they are set.

1. Add an implicit animation

#

Change the Container widget to an AnimatedContainer widget:

{container1 → container2}/lib/main.dart RENAMED
@@ -47,7 +47,7 @@
47
47
  SizedBox(
48
48
  width: 128,
49
49
  height: 128,
50
- child: Container(
50
+ child: AnimatedContainer(
51
51
  margin: EdgeInsets.all(margin),
52
52
  decoration: BoxDecoration(
53
53
  color: color,

2. Set starting values for animated properties

#

The AnimatedContainer widget transitions between old and new values of its properties when they change. To contain the behavior triggered when the user clicks Change, create a change() method. The change() method can use the setState() method to set new values for the color, borderRadius, and margin state variables:

{container2 → container3}/lib/main.dart RENAMED
@@ -38,6 +38,14 @@
38
38
  margin = randomMargin();
39
39
  }
40
+ void change() {
41
+ setState(() {
42
+ color = randomColor();
43
+ borderRadius = randomBorderRadius();
44
+ margin = randomMargin();
45
+ });
46
+ }
47
+
40
48
  @override
41
49
  Widget build(BuildContext context) {
42
50
  return Scaffold(

3. Set up a trigger for the animation

#

To set the animation to trigger whenever the user presses Change, invoke the change() method in the onPressed() handler:

{container3 → container4}/lib/main.dart RENAMED
@@ -65,7 +65,7 @@
65
65
  ),
66
66
  ElevatedButton(
67
67
  child: const Text('Change'),
68
- onPressed: () => {},
68
+ onPressed: () => change(),
69
69
  ),
70
70
  ],
71
71
  ),

4. Set duration

#

Set the duration of the animation that powers the transition between the old and new values:

{container4 → container5}/lib/main.dart RENAMED
@@ -6,6 +6,8 @@
6
6
  import 'package:flutter/material.dart';
7
+ const _duration = Duration(milliseconds: 400);
8
+
7
9
  double randomBorderRadius() {
8
10
  return Random().nextDouble() * 64;
9
11
  }
@@ -61,6 +63,7 @@
61
63
  color: color,
62
64
  borderRadius: BorderRadius.circular(borderRadius),
63
65
  ),
66
+ duration: _duration,
64
67
  ),
65
68
  ),
66
69
  ElevatedButton(

Shape-shifting (complete)

#

Here's the example with the completed changes you've made. Run the code and click Change to trigger the animation. Each time you click Change, the shape animates to its new values for margin, borderRadius, and color.

// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.

import 'dart:math';

import 'package:flutter/material.dart';

const _duration = Duration(milliseconds: 400);

double randomBorderRadius() {
  return Random().nextDouble() * 64;
}

double randomMargin() {
  return Random().nextDouble() * 64;
}

Color randomColor() {
  return Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
}

class AnimatedContainerDemo extends StatefulWidget {
  const AnimatedContainerDemo({super.key});

  @override
  State<AnimatedContainerDemo> createState() => _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
  late Color color;
  late double borderRadius;
  late double margin;

  @override
  void initState() {
    super.initState();
    color = randomColor();
    borderRadius = randomBorderRadius();
    margin = randomMargin();
  }

  void change() {
    setState(() {
      color = randomColor();
      borderRadius = randomBorderRadius();
      margin = randomMargin();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            SizedBox(
              width: 128,
              height: 128,
              child: AnimatedContainer(
                margin: EdgeInsets.all(margin),
                decoration: BoxDecoration(
                  color: color,
                  borderRadius: BorderRadius.circular(borderRadius),
                ),
                duration: _duration,
              ),
            ),
            ElevatedButton(
              child: const Text('Change'),
              onPressed: () => change(),
            ),
          ],
        ),
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: AnimatedContainerDemo(),
    );
  }
}

void main() {
  runApp(
    const MyApp(),
  );
}

Using animation curves

#

The preceding examples show how:

  • Implicit animations allow you to animate the transition between values for specific widget properties.
  • The duration parameter allows you to set how long the animation takes to complete.

Implicit animations also allow you to control changes to the rate of an animation that occurs during the set duration. To define this change in rate, set the value of the curve parameter to a Curve, such as one declared in the Curves class.

The preceding examples did not specify a value for the curve parameter. Without a specified curve value, the implicit animations apply a linear animation curve.

Specify a value for the curve parameter in the complete shape-shifting example. The animation changes when you pass the easeInOutBack constant for curve,

{container5 → container6}/lib/main.dart RENAMED
@@ -64,6 +64,7 @@
64
64
  borderRadius: BorderRadius.circular(borderRadius),
65
65
  ),
66
66
  duration: _duration,
67
+ curve: Curves.easeInOutBack,
67
68
  ),
68
69
  ),
69
70
  ElevatedButton(

When you pass the Curves.easeInOutBack constant to the curve property of the AnimatedContainer widget, watch how the rates of change for margin, borderRadius, and color follow the curve that constant defined.

Putting it all together

#

The complete shape-shifting example animates transitions between values for margin, borderRadius, and color properties. The AnimatedContainer widget animates changes to any of its properties. These include those you didn't use such as padding, transform, and even child and alignment! By showing additional capabilities of implicit animations, the complete shape-shifting example builds upon fade-in complete example.

To summarize implicit animations:

  • Some implicit animations, like the AnimatedOpacity widget, only animate one property. Others, like the AnimatedContainer widget, can animate many properties.
  • Implicit animations animate the transition between the old and new value of a property when it changes using the provided curve and duration.
  • If you do not specify a curve, implicit animations default to a linear curve.

What's next?

#

Congratulations, you've finished the codelab! To learn more, check out these suggestions: