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)
#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:
@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'),
],
),
AnimatedOpacity(
child: const Column(
children: [
Text('Type: Owl'),
Text('Age: 39'),
Text('Employment: None'),
],
),
),
]);
}
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:
class _FadeInDemoState extends State<FadeInDemo> {
double opacity = 0;
@override
Widget build(BuildContext context) {
return ListView(children: <Widget>[
// ...
AnimatedOpacity(
opacity: opacity,
child: const Column(
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:
AnimatedOpacity(
duration: const Duration(seconds: 2),
opacity: opacity,
child: const Column(
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:
TextButton(
child: const Text(
'Show Details',
style: TextStyle(color: Colors.blueAccent),
),
onPressed: () => {},
onPressed: () => setState(() {
opacity = 1;
}),
),
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 foropacity
. - 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 aborderRadius
,margin
, andcolor
. 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 aColor
for thecolor
property - The
randomBorderRadius()
function generates adouble
for theborderRadius
property. - The
randomMargin()
function generates adouble
for themargin
property.
The following steps use the AnimatedContainer
widget to:
- Transition to new values for
color
,borderRadius
, andmargin
whenever the user clicks Change. - 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:
SizedBox(
width: 128,
height: 128,
child: Container(
child: AnimatedContainer(
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(borderRadius),
),
),
),
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:
void change() {
setState(() {
color = randomColor();
borderRadius = randomBorderRadius();
margin = randomMargin();
});
}
@override
Widget build(BuildContext context) {
// ...
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:
ElevatedButton(
child: const Text('Change'),
onPressed: () => {},
onPressed: () => change(),
),
4. Set duration
#Set the duration
of the animation that powers the transition between the old and new values:
SizedBox(
width: 128,
height: 128,
child: AnimatedContainer(
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(borderRadius),
),
duration: const Duration(milliseconds: 400),
),
),
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
,
SizedBox(
width: 128,
height: 128,
child: AnimatedContainer(
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(borderRadius),
),
duration: _duration,
curve: Curves.easeInOutBack,
),
),
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 theAnimatedContainer
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
andduration
. - 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:
- Try the animations tutorial.
- Learn about hero animations and staggered animations.
- Checkout the animation library.
- Try another codelab.
Unless stated otherwise, the documentation on this site reflects the latest stable version of Flutter. Page last updated on 2024-08-16. View source or report an issue.