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:
- How to make a Flutter app.
- How to use stateful widgets.
This codelab covers the following material:
- Using
AnimatedOpacityto create a fade-in effect. - Using
AnimatedContainerto 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)
Click the Run button to run the example:
{$ begin main.dart $}
import 'package:flutter/material.dart';
const owl_url = 'https://raw.githubusercontent.com/flutter/website/master/src/images/owl.jpg';
class FadeInDemo extends StatefulWidget {
_FadeInDemoState createState() => _FadeInDemoState();
}
class _FadeInDemoState extends State<FadeInDemo> {
@override
Widget build(BuildContext context) {
return Column(children: <Widget>[
Image.network(owl_url),
TextButton(
child: Text(
'Show details',
style: TextStyle(color: Colors.blueAccent),
),
onPressed: () => null,
),
Container(
child: Column(
children: <Widget>[
Text('Type: Owl'),
Text('Age: 39'),
Text('Employment: None'),
],
),
)
]);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: FadeInDemo(),
),
),
);
}
}
void main() {
runApp(
MyApp(),
);
}
{$ end main.dart $}
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 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 the Show details button.
- When the user clicks the Show details button, 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 the
AnimatedOpacity widget. Change the Container widget to an
AnimatedOpacity widget:
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
style: TextStyle(color: Colors.blueAccent),
|
|
22
22
|
),
|
|
23
23
|
onPressed: () => null),
|
|
24
|
-
|
|
24
|
+
AnimatedOpacity(
|
|
25
25
|
child: Column(
|
|
26
26
|
children: <Widget>[
|
|
27
27
|
Text('Type: Owl'),
|
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:
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
}
|
|
12
12
|
class _FadeInDemoState extends State<FadeInDemo> {
|
|
13
|
+
double opacity = 0.0;
|
|
14
|
+
|
|
13
15
|
@override
|
|
14
16
|
Widget build(BuildContext context) {
|
|
15
17
|
return Column(children: <Widget>[
|
|
@@ -22,6 +24,8 @@
|
|
|
22
24
|
),
|
|
23
25
|
onPressed: () => null),
|
|
24
26
|
AnimatedOpacity(
|
|
27
|
+
duration: Duration(seconds: 3),
|
|
28
|
+
opacity: opacity,
|
|
25
29
|
child: Column(
|
|
26
30
|
children: <Widget>[
|
|
27
31
|
Text('Type: Owl'),
|
3. Set up a trigger for the animation, and choose an end value
Configure the animation to trigger when the user clicks the Show details
button. To do this, change opacity state using the onPressed() handler for
TextlButton. To make the FadeInDemo widget become fully visible when
the user clicks the Show details button, use the onPressed() handler
to set opacity to 1:
|
@@ -18,11 +18,14 @@
|
|
|
18
18
|
return Column(children: <Widget>[
|
|
19
19
|
Image.network(owl_url),
|
|
20
20
|
TextButton(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
child: Text(
|
|
22
|
+
'Show Details',
|
|
23
|
+
style: TextStyle(color: Colors.blueAccent),
|
|
24
|
+
),
|
|
25
|
+
onPressed: () => setState(() {
|
|
26
|
+
opacity = 1;
|
|
27
|
+
}),
|
|
28
|
+
),
|
|
26
29
|
AnimatedOpacity(
|
|
27
30
|
duration: Duration(seconds: 2),
|
|
28
31
|
opacity: opacity,
|
4. 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:
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
),
|
|
25
25
|
onPressed: () => null),
|
|
26
26
|
AnimatedOpacity(
|
|
27
|
-
duration: Duration(seconds:
|
|
27
|
+
duration: Duration(seconds: 2),
|
|
28
28
|
opacity: opacity,
|
|
29
29
|
child: Column(
|
|
30
30
|
children: <Widget>[
|
Fade-in (complete)
Here’s the example with the completed changes you’ve made—run this example and click the Show details button to trigger the animation.
{$ begin main.dart $}
import 'package:flutter/material.dart';
const owl_url = 'https://raw.githubusercontent.com/flutter/website/master/src/images/owl.jpg';
class FadeInDemo extends StatefulWidget {
_FadeInDemoState createState() => _FadeInDemoState();
}
class _FadeInDemoState extends State<FadeInDemo> {
double opacityLevel = 0.0;
@override
Widget build(BuildContext context) {
return Column(children: <Widget>[
Image.network(owl_url),
TextButton(
child: Text(
'Show details',
style: TextStyle(color: Colors.blueAccent),
),
onPressed: () => setState(() {
opacityLevel = 1.0;
}),
),
AnimatedOpacity(
duration: Duration(seconds: 3),
opacity: opacityLevel,
child: Column(
children: <Widget>[
Text('Type: Owl'),
Text('Age: 39'),
Text('Employment: None'),
],
),
)
]);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: FadeInDemo(),
),
),
);
}
}
void main() {
runApp(
MyApp(),
);
}
{$ end main.dart $}
Putting it all together
The Fade-in text effect example demonstrates the following features
of AnimatedOpacity:
-
AnimatedOpacitylistens for state changes in itsopacityproperty. - Whenever
opacitychanges,AnimatedOpacityautomatically animates the widget’s transition to the new value foropacity. -
AnimatedOpacityrequires adurationparameter to define the time it takes to animate the transition between an oldopacityvalue and a new one.
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
ContainerwithborderRadius,margin, andcolorproperties that are different each time you run the example. - A Change button that does nothing when clicked.
Shape-shifting (starter code)
Click the Run button to run the example:
{$ begin main.dart $}
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 {
_AnimatedContainerDemoState createState() => _AnimatedContainerDemoState();
}
class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
late Color color;
late double borderRadius;
late double margin;
@override
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: Text('change'),
onPressed: () => null,
),
],
),
),
);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: AnimatedContainerDemo(),
);
}
}
void main() {
runApp(
MyApp(),
);
}
{$ end main.dart $}
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 the steps, you can also run the shape-shifting complete example with the changes already made.
In the shape-shifting starter code,
each property in the Container widget (color,
borderRadius, and margin)
is assigned a value by an associated function (randomColor(),
randomBorderRadius(), and randomMargin() respectively).
By using an AnimatedContainer widget,
you can refactor this code to do the following:
- Generate new values for
color,borderRadius, andmarginwhenever the user clicks the Change button. - Animate the transition to the new values for
color,borderRadius, andmarginwhenever they are set.
1. Add an implicit animation
Change the Container widget to an AnimatedContainer widget:
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
SizedBox(
|
|
45
45
|
width: 128,
|
|
46
46
|
height: 128,
|
|
47
|
-
child:
|
|
47
|
+
child: AnimatedContainer(
|
|
48
48
|
margin: EdgeInsets.all(margin),
|
|
49
49
|
decoration: BoxDecoration(
|
|
50
50
|
color: color,
|
2. Set starting values for animated properties
AnimatedContainer automatically animates between old and new values of
its properties when they change. Create a change() method that defines the
behavior triggered when the user clicks the Change button.
The change() method can use setState() to set new values
for the color, borderRadius, and margin state variables:
|
@@ -35,6 +35,14 @@
|
|
|
35
35
|
margin = randomMargin();
|
|
36
36
|
}
|
|
37
|
+
void change() {
|
|
38
|
+
setState(() {
|
|
39
|
+
color = randomColor();
|
|
40
|
+
borderRadius = randomBorderRadius();
|
|
41
|
+
margin = randomMargin();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
@override
|
|
38
46
|
Widget build(BuildContext context) {
|
|
39
47
|
return Scaffold(
|
3. Set up a trigger for the animation
To set the animation to trigger whenever the user presses the Change button,
invoke the change() method in the onPressed() handler:
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
),
|
|
63
63
|
ElevatedButton(
|
|
64
64
|
child: Text('change'),
|
|
65
|
-
onPressed: () =>
|
|
65
|
+
onPressed: () => change(),
|
|
66
66
|
),
|
|
67
67
|
],
|
|
68
68
|
),
|
4. Set duration
Finally, set the duration of the animation that powers the transition
between the old and new values:
|
@@ -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
|
}
|
|
@@ -58,6 +60,7 @@
|
|
|
58
60
|
color: color,
|
|
59
61
|
borderRadius: BorderRadius.circular(borderRadius),
|
|
60
62
|
),
|
|
63
|
+
duration: _duration,
|
|
61
64
|
),
|
|
62
65
|
),
|
|
63
66
|
ElevatedButton(
|
Shape-shifting (complete)
Here’s the example with the completed changes you’ve made—run the code
and click the Change button to trigger the animation. Notice that each time
you click the Change button, the shape animates to its new values
for margin, borderRadius, and color.
{$ begin main.dart $}
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 {
_AnimatedContainerDemoState createState() => _AnimatedContainerDemoState();
}
class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
late Color color;
late double borderRadius;
late double margin;
@override
void initState() {
super.initState();
color = Colors.deepPurple;
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: Text('change'),
onPressed: () => change(),
),
],
),
),
);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: AnimatedContainerDemo(),
);
}
}
void main() {
runApp(
MyApp(),
);
}
{$ end main.dart $}
Using animation curves
The preceding examples show how implicit animations allow you to animate
changes in values for specific widget properties, and how the
duration parameter allows you to set the amount of time an
animation takes to complete. Implicit animations also allow you to
control changes to the rate of an animation within the duration.
The parameter you use to define this change in rate is curve.
The preceding examples do not specify a curve,
so the implicit animations apply a linear animation curve by default.
Add a curve parameter to the shape-shifting complete
and watch how the animation changes when you pass the
easeInOutBack constant for curve:
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
borderRadius: BorderRadius.circular(borderRadius),
|
|
62
62
|
),
|
|
63
63
|
duration: _duration,
|
|
64
|
+
curve: Curves.easeInOutBack,
|
|
64
65
|
),
|
|
65
66
|
),
|
|
66
67
|
ElevatedButton(
|
Now that you have passed easeInOutBack as the value for curve to
AnimatedContainer, notice that the rates of change for margin,
borderRadius, and color follow the curve defined by the
easeInOutBack curve:
Putting it all together
The shape-shifting complete example animates transitions between values for
margin, borderRadius, and color properties.
Note that AnimatedContainer animates changes to any of its properties,
including those you didn’t use such as padding, transform,
and even child and alignment!
The shape-shifting complete example builds upon fade-in complete by showing
additional capabilities of implicit animations:
- Some implicit animations (for example,
AnimatedOpacity) only animate a single property, while others (likeAnimatedContainer) can animate many properties. - Implicit animations automatically animate between the old and
new values of properties when they change using the provided
curveandduration. - If you do not specify a
curve, implicit animations default to a linear curve.
What’s next?
Congratulations, you’ve finished the codelab! If you’d like to learn more, here are some suggestions for where to go next:
- Try the animations tutorial.
- Learn about hero animations and staggered animations.
- Checkout the animation library.
- Try another codelab.