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
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)
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
:
-
AnimatedOpacity
listens for state changes in itsopacity
property. - Whenever
opacity
changes,AnimatedOpacity
automatically animates the widget’s transition to the new value foropacity
. -
AnimatedOpacity
requires aduration
parameter to define the time it takes to animate the transition between an oldopacity
value 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
Container
withborderRadius
,margin
, andcolor
properties 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
, andmargin
whenever the user clicks the Change button. - Animate the transition to the new values for
color
,borderRadius
, andmargin
whenever 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
curve
andduration
. - 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.