Building off of the concepts of animation within Flutter, now we want to look at how we’ll create animations within Flutter.
Creating a New Basic App
Let’s look at a simple example. To do so, we’re going to create a new Flutter app.
Next, we are going to replace the code generated with the following code:
import 'package:flutter/material.dart';
void main() => runApp(const LogoApp());
class LogoApp extends StatefulWidget {
const LogoApp({Key? key}) : super(key: key);
@override
_LogoAppState createState() => _LogoAppState();
}
class _LogoAppState extends State<LogoApp> {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: 250,
width: 250,
child: const FlutterLogo(),
),
);
}
}
This app will display the built in Flutter logo, but not have any animation initially. You’ll also notice it has no appbar, or text, etc. This is a bare bones example to showcase just the animation we’ll be doing later
Notice that our main() function is redirected to the runApp()
function, which created an LogoApp
.
Within _LogoAppState
, we create a widget which contains the FlutterLogo()
, which is centered. You can change the size of the logo, by adjusting the height and width properties.
Try adjusting the height and width properties, to values such as 100, 350, and even 500. You will notice that if you do not keep the values the same, Flutter will essentially do this for you.
Notice that we’ve created a stateful widget, because it will need to remember the widget components to know where/how to animate them.
Adding Animation
To add animation we will have to make several changes.
First we will need to import a new library, flutter/animation.dart
This will be done at the top of the main.dart file, as seen below.
import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
Note that the order of the import doesn’t have an effect on it’s ability to run.
Our private class _LogoAppState definition will need to be modified as well. We will add the with SingleTickerProviderStateMixin to the class definition line, and add two new properties that will be used later.
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
// snip
Those new attributes will be used to control the animation.
However, we have only defined this attributes, we need to initialize them with the following code. Notice that we are overriding a method that exists, so that it will automatically be called.
@override
void initState() {
super.initState();
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = Tween<double>(begin: 0, end: 300).animate(controller)
..addListener(() {
setState(() {
// The state that has changed here is the animation object’s value.
});
});
controller.forward();
}
Tip: You might not be familiar with Dart’s cascade notation—the two dots in ..addListener()
. This syntax means that the addListener()
method is called with the return value from animate()
.
The initState
, defines the initial state of the Widget. We call super.initState()
so that the base class runs as expected, then add some additional code to run, which initializes our AnimationController
. When we create this object, we define the duration (2 seconds). If we change this value we can make our animation seem faster or slower.
Notice that we define a beginning and an ending value for which to animation with the animation object.
The controller.forward() starts the animation in a forward motion through time. You do not have to move forward, but it tends to make the most sense.
Next, our next change will be to modify the height and width properties of the logo. Instead of being defined explicitly, it will be defined based upon the animation.
height: animation.value,
width: animation.value,
Finally, we will also need to create a new method which overrides a built in method, the dispose
method.
@override
void dispose() {
controller.dispose();
super.dispose();
}
The dispose
method, calls the original dispose
method, but only after it disposes of the controller object, which has not be implemented yet.
Note: the dispose
and initState
both override methods within State, so they should exist within the class _LogoAppState
.
Monitoring the Progress of the Animation
We can add a listener so we know when a state changes in an animation. This is can helpful for determining the next step to perform, or for even debugging purposes.
To do so, we’re going to add a new listener to our code after the animation object is created. It will make that line:
animation = Tween<double>(begin: 0, end: 300).animate(controller)
..addStatusListener((state) => print('$state'));
If you do a hot reload, you will notice the following output to your terminal window:
=== terminal output ===
AnimationStatus.forward
AnimationStatus.completed
We can use this to determine when the state completed has been reached. We can then reverse the animation, so it goes back and forth.
animation = Tween<double>(begin: 0, end: 300).animate(controller)
..addListener(() {
setState(() {
// The state that has changed here is the animation object’s value.
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
})
..addStatusListener((state) => print('$state'));
Notice how we have added a new status event listener. Within that anonymous function, we check to see what the status is, and if it is a specific value, we reverse the animation, or set it forward again.
The completed
attribute is for when the animation has completed moving forward until the end.
The dismissed
attribute is for when the animation has completed moving from the end to the starting position.
Animations Examples in Flutter was originally found on Access 2 Learn
One Comment
Comments are closed.