Implicit Animations in Flutter: Getting Started
Learn how to make smooth-flowing and beautiful apps by adding implicit animations to your Flutter project’s buttons, containers and screen transitions. By Yogesh Choudhary.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Implicit Animations in Flutter: Getting Started
30 mins
- Getting Started
- Understanding the Starter Project
- Animations in Flutter
- Seeing an Animation in Slow Motion
- Explicit and Implicit Animations
- Animating Buttons
- Making a Responsive TextField
- Using the Cross-fade Effect Between Widgets
- Animating Page Transitions
- Creating a Radial Menu
- Adding AnimatedSwitcher to Your Radial Menu
- Creating the Radial Menu Widget
- Adding AnimatedPositioned to the Radial Menu
- Implementing a Circular Progress Bar
- Where to Go From Here?
Animating Buttons
AnimatedContainer
is an animated version of Container
. It's a powerful animation widget that allows animations on most properties of Container
. You'll use this widget to change the color of the Let's go button on the initial screen from grey to green in an animated way, instead of having the same color all the time.
Open greetings_screen.dart in the screens folder. Scroll down until you find _userNameInput
. Inside that method, you'll see the onChanged
callback of TextFieldForm
with a // TODO
comment. Directly below that comment, add the following code:
if (v.length > 2) {
setState(() {
_color = Colors.green;
});
} else {
setState(() {
_color = Colors.grey;
});
}
The text field calls the onChanged
callback whenever the user changes the text in the field. In this case, the _color
field will set to green if there's more than two characters in the textfield. If there's less than or equal to two characters it'll be set to grey. You're using setState
to tell the Flutter framework that it should rebuild the user interface.
While still in greetings_screen.dart, replace _getButton
with the following code:
Widget _getButton() {
// 1
return AnimatedContainer(
margin: const EdgeInsets.only(top: 10.0),
// 2
duration: const Duration(milliseconds: 900),
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
// 3
color: _color,
borderRadius: const BorderRadius.all(
Radius.circular(15.0),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
offset: const Offset(1.1, 1.1),
blurRadius: 10.0),
],
),
child: InkWell(
onTap: () {
if (name.length > 2) {
if (!_currentFocus.hasPrimaryFocus) {
_currentFocus.unfocus();
}
setState(() {
// TODO
_showNameLabel = true;
});
}
},
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 3.0),
child: Text('Let\'s go'),
),
),
);
}
Here is the explanation for the numbered comments:
-
AnimatedContainer
is the name of the animated version ofContainer
. - The
duration
parameter is a required parameter for any animation widget. It takes aDuration
object, which tells the animation widget how long the animation should take to complete. - You're assigning
_color
to thecolor
parameter. A change in this variable triggers the animation. The animated widget then animates the color for the given duration.
Finally, change the initial color of the button to gray. Update the _color
declaration at the top of GreeetingsScreenState
to the following:
Color _color = Colors.grey;
Hot reload the app to see the difference:
Since you're using the AnimatedContainer
class, any property changes on the AnimatedContainer
will automatically animate without you having to specify any messy animation code. Pretty nifty, huh?
Making a Responsive TextField
Making a responsive button is good, but the user interface could be better. It's time to make the text field more responsive so that whenever the user selects it to type something, its width increases to the maximum.
To achieve this, you'll use AnimatedSize
. This animation widget changes its size over a given duration of time whenever its child widget changes its size. All you need to do to use this widget is provide it a duration and a child.
To prepare for that change, first replace this line:
class GreetingsScreenState extends State<GreetingsScreen> {
with the following:
class GreetingsScreenState extends State<GreetingsScreen>
with TickerProviderStateMixin {
You'll use TickerProviderStateMixin
to provide a Ticker
to your animation.
Add the following property to GreetingsScreenState
:
bool _textFieldSelected = false;
Now, you're ready to add the animation. You're going to wrap the text field with AnimatedSize
. To do that, replace _userNameInput
with the following code:
Widget _userNameInput() {
return AnimatedSize(
// 1
duration: const Duration(milliseconds: 500),
// 2
vsync: this,
child: Container(
width: _textFieldSelected ? double.infinity : 3 * _screenWidth / 4,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: TextFieldForm(
onTap: () {
setState(() {
// 3
_textFieldSelected = true;
});
},
onChanged: (v) {
name = v;
if (v.length > 2) {
setState(() {
_color = Colors.green;
});
} else {
setState(() {
_color = Colors.grey;
});
}
},
),
),
);
}
Here's an explanation of the code:
-
duration
sets the time the animation takes to move to full width and back again. -
vsync
is aTickerProvider
that will provide theTicker
to your animation widget. These objects are responsible for listening to the screen refresh rate of your smartphone and providing a refresh rate for the animations. Since you addedTickerProviderStateMixin
to this class,this
handles it. - When
_textFieldSelected
changes, the animation starts.
For the last piece of the puzzle, you'll use a gesture detector to detect when you click outside the text field.
Go to build
in GreetingsScreenState
and locate the onTap
parameter of GestureDetector
. Add the following lines to the top of the onTap
callback:
setState(() {
_textFieldSelected = false;
});
Tapping anywhere outside the text field will let the animator know it needs to return to its original state.
Build and run again:
This time, everything works as expected. Nice! :]
Using the Cross-fade Effect Between Widgets
The next animation widget you'll work with is AnimationCrossFade
. This widget is a simple and fast way to get a cross-fade effect between widgets. Using it with widgets of the same size is straightforward but, if you have different widget sizes, you might need a custom layoutBuilder
to avoid sudden jumps in animation.
You'll use AnimationCrossFade
to fade in the greetings box and fade out the text field. Add the following to GreetingsScreenState
:
bool _showWelcomeMessage = false;
Widget _getAnimatedCrossFade() {
return AnimatedCrossFade(
//1
firstChild: _userInputField(),
secondChild: _welcomeMessage(),
//2
crossFadeState: _showWelcomeMessage
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
//3
duration: const Duration(milliseconds: 900),
);
}
Here's what you just did:
- The parameters
firstChild
andsecondChild
take the widgets you want to animate between. -
crossFadeState
takes the child that will be visible after the animation has completed. When_showWelcomeMessage
istrue
, it'll show the_welcomeMessage
widget. Otherwise,_userInputField
would be visible after the end of the animation. - You define how long the animation will take.
Now, you need to make a couple of adjustments. Go to _getButton
and find the // TODO
comment. Replace _showNameLabel = true;
with the following line:
_showWelcomeMessage = true;
Next, go to build
, find the line !_showNameLabel ? _userInputField() : _welcomeMessage(),
and replace it with the following:
_getAnimatedCrossFade(),
Now instead of abruptly switching between showing the name input field or the welcome message, you'll do a nice controlled cross fade from one to the other.
That's it. Save the file and re-run your app. You'll be able to see the cross-fade effect: