As a mobile UI framework, Flutter also has its own animation system.
classification
Flutter animations fall into two categories: Tween and physics-based animations.
This article focuses on the first type of animation.
The basic class of animation
Animation<T>
Animation is an abstract class that stores the state and current value of an Animation. The most commonly used Animation class is Animation<double>
T has many types, such as Color and Offset. More on that later
The value of the current Animation can be obtained by using the value property in the Animation.
Monitor for animation:
- AddListener () The listener for each frame of animation execution
- AddStatusListener () listens for changes in animation state. There are four states
AnimationController
The AnimationController inherits Animation<double> and controls the execution, stopping, and so on of the Animation.
The AnimationController will generate a new value every frame of the animation. By default, the AnimationController generates linear numbers from 0.0 to 1.0 (the default range) over a given period of time.
To create the AnimationController, pass in a vsync parameter.
Tween
By default, the AnimationController object ranges from 0.0 to 1.0.
If you need different ranges or data types, you need to tween to configure the animation to generate values for different ranges or data types
The Tween subclass looks like this:
example
class PageState extends State<HomePage> with SingleTickerProviderStateMixin {
AnimationController controller;
// Doubler type animation
Animation<double> doubleAnimation;
// Color animation
Animation<Color> colorAnimation;
// Position animation
Animation<Offset> offsetAnimation;
// Rounded corner animation
Animation<BorderRadius> radiusAnimation;
// Decorate the animation
Animation<Decoration> decorationAnimation;
// Font animation
Animation<TextStyle> textStyleAnimation;
@override
void initState() {
// TODO: implement initState
super.initState();
/ / create AnimationController
controller = new AnimationController(
vsync: this, duration: Duration(milliseconds: 2000));
// The first way to create an animation:
doubleAnimation = new Tween<double>(begin: 0.0, end: 200.0).animate(controller) .. addListener(() { setState(() {}); }).. addStatusListener((AnimationStatus status) {// Execute the command in reverse order
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
// The reverse execution is complete and the forward execution is completecontroller.forward(); }});// The second way to create an animation is:
offsetAnimation = controller.drive(
Tween<Offset>(begin: Offset(0.0.0.0),end: Offset(400.0.200.0))); colorAnimation = ColorTween(begin: Colors.yellow,end: Colors.red).animate(controller); radiusAnimation = BorderRadiusTween(begin: BorderRadius.circular(0),end: BorderRadius.circular(50)).animate(controller);
decorationAnimation = DecorationTween(begin: BoxDecoration(color: Colors.purple,borderRadius: BorderRadius.circular(0),),
end: BoxDecoration(color: Colors.lightBlueAccent,borderRadius: BorderRadius.circular(40))).animate(controller);
textStyleAnimation = TextStyleTween(begin: TextStyle(color: Colors.black,fontSize: 20,fontWeight: FontWeight.w100),
end: TextStyle(color: Colors.purple,fontSize: 30,fontWeight: FontWeight.w700)).animate(controller);
// Start animation
controller.forward();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(title: Text("Tween animation"),),
body: Container(
alignment: Alignment.center,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 200,
child: Container(
height: doubleAnimation.value,
width: doubleAnimation.value,
child: FlutterLogo(),
),
),
Container(
margin: EdgeInsets.only(left: offsetAnimation.value.dx),
width: 50,
height: 50,
color: Colors.green,
),
Container(
height: 100,
width: 100,
color: colorAnimation.value,
),
SizedBox(height: 10,),
Container(
height: 100,
width: 100,
decoration: BoxDecoration(borderRadius: radiusAnimation.value,color: Colors.blue),
),
Container(
height: 60,
width: 200,
decoration: decorationAnimation.value,
),
Container(
height: 100,
child: Text("TestStyleTween",style: textStyleAnimation.value,),
),
],
),
)
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose(); controller.dispose(); }}Copy the code
Tween animation execution renderings
When we have a complex layout in state, we call setState to refresh the widget tree with every frame change, redrawing all the widgets in state and causing unnecessary performance drain. We only need to refresh the widget where the animation is performed. Flutter provides us with animatedWidgets.
AnimatedWidget
The source code
abstract class AnimatedWidget extends StatefulWidget {
// Create a widget to refactor when ListEnable changes
const AnimatedWidget({
Key key,
@required this.listenable,
}) : assert(listenable ! =null),
super(key: key);
// Declare a Listenable, frame animation listener
final Listenable listenable;
@protected
Widget build(BuildContext context);
/// Subclasses typically do not override this method.
@override_AnimatedState createState() => _AnimatedState(); . }Copy the code
The AnimatedWidget inherits its state from the StatefulWidget and instantiates a ListEnable to listen for frame animation and refresh the AnimatedWidget when the animation method changes.
The source code for the __AnimatedState method is as follows:
class _AnimatedState extends State<AnimatedWidget> {
@override
void initState() {
super.initState();
// Add a listening callback
widget.listenable.addListener(_handleChange);
}
@override
void didUpdateWidget(AnimatedWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.listenable != oldWidget.listenable) {
oldWidget.listenable.removeListener(_handleChange);
widget.listenable.addListener(_handleChange);
}
}
@override
void dispose() {
widget.listenable.removeListener(_handleChange);
super.dispose();
}
// Trigger a refresh when the frame animation changes
void _handleChange() {
setState(() {
// The listenable's state is our build state, and it changed already.
});
}
// Call the build() method to reconstruct the AnimatedWidget
@override
Widget build(BuildContext context) => widget.build(context);
}
Copy the code
When the Refresh is triggered by ListEnable, setState is called to refactor the AnimatedWidget, although setState is still called in the end, but the refreshed object is different.
Listenable
From the source code, you can see that a Listenable is declared in the AnimatedWidget to listen for changes to each frame. So what is Listenable. We can take a look at the Animation source code:
abstract class Listenable {
const Listenable();
factory Listenable.merge(List<Listenable> listenables) = _MergingListenable;
void addListener(VoidCallback listener);
void removeListener(VoidCallback listener);
}
Copy the code
So Animation is also a Listenable implementation class. Read the source code, the specific usage is as follows:
ColorAnimationWidget
Declare a ColorAnimationWidget class that inherits from AnimatedWidget. The code is as follows:
class ColorAnimationWidget extends AnimatedWidget{
ColorAnimationWidget({Key key, Animation<Color> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final Animation<Color> animation = listenable;
// TODO: implement build
return Center(
child: Container(
width: 200,
height: 200, color: animation.value, ), ); }}Copy the code
Using ColorAnimationWidget
class PageState extends State<HomePage> with SingleTickerProviderStateMixin{
AnimationController _controller;
Animation<Color> _animation;
@override
void initState() {
// TODO: implement initState
super.initState();
_controller = AnimationController(vsync: this,duration: Duration(seconds: 2)); _animation = ColorTween(begin: Colors.lightBlueAccent,end: Colors.red).animate(_controller) .. addStatusListener((AnimationStatus status){if(status == AnimationStatus.completed){
_controller.reverse();
}else if(status == AnimationStatus.dismissed){ _controller.forward(); }}); _controller.forward(); }@override
Widget build(BuildContext context) {
// TODO: implement build
returnScaffold( body: ColorAnimationWidget(animation: _animation,), ); }}class ColorAnimationWidget extends AnimatedWidget{
ColorAnimationWidget({Key key, Animation<Color> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final Animation<Color> animation = listenable;
// TODO: implement build
return Center(
child: Container(
width: 200,
height: 200, color: animation.value, ), ); }}Copy the code
You can see that the usage is basically the same as without the AnimatedWidget, with the only difference being that setState is called within the AnimatedWidget and only one widget is refreshed.
ColorAnimationWidget
CurvedAnimation
By default, Tween animation provides linear changes within the interval. If we need curve changes, we need to use CurvedAnimation together. The elastic effect is shown below:
class HomePageState extends State<HomePage> with TickerProviderStateMixin{
Animation animation;
AnimationController controller;
@override
void initState() {
// TODO: implement initState
super.initState();
controller = new AnimationController(vsync: this,duration: Duration(seconds: 2));
CurvedAnimation curve = new CurvedAnimation(parent: controller, curve: Curves.bounceOut);
animation = Tween<double>(begin: 0.0,end: 500).animate(curve) .. addListener((){ setState(() { }); }); controller.forward(); }@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
alignment: Alignment.topCenter,
child: Container(
margin: EdgeInsets.only(top: animation.value),
width: 100,
height: 100, child: FlutterLogo(), ), ); }}Copy the code
CurvedAnimation curve = new CurvedAnimation(parent: controller, curve: Curves.bounceOut); Here we use the bounceOut effect
For more Curves, see the Curves on the Curves website
Let’s look at how bounceOut is implemented:
class _BounceOutCurve extends Curve {
const _BounceOutCurve._();
@override
double transformInternal(double t) {
return_bounce(t); }}double _bounce(double t) {
if (t < 1.0 / 2.75) {
return 7.5625 * t * t;
} else if (t < 2 / 2.75) {
t -= 1.5 / 2.75;
return 7.5625 * t * t + 0.75;
} else if (t < 2.5 / 2.75) {
t -= 2.25 / 2.75;
return 7.5625 * t * t + 0.9375;
}
t -= 2.625 / 2.75;
return 7.5625 * t * t + 0.984375;
}
Copy the code
BounceOut inherits Curve, implements its transformInternal method, and implements its trajectory in transformInternal.
AnimatedBuilder
We implemented a color change example above. What if we now need to implement a size change widget? Do you want to extend the AnimationWidget after declaring a SizeAnimationWidget?
Obviously it’s ok to do that. Better yet, use AnimatedBuilder to refactor our widget.
What is a AnimatedBuilder
The AnimatedBuilder inherits from the abstract AnimationWidget in order to build a generic AnimationWidget implementation class without having to create an implementation class each time an AnimationWidget is used.
AnimatedBuilder source code:
class AnimatedBuilder extends AnimatedWidget {
/// Creates an animated builder.
const AnimatedBuilder({
Key key,
@required Listenable animation,
@required this.builder,
this.child,
}) : assert(animation ! =null),
assert(builder ! =null),
super(key: key, listenable: animation);
final TransitionBuilder builder;
final Widget child;
@override
Widget build(BuildContext context) {
returnbuilder(context, child); }}Copy the code
All we need to do is pass in the Animation and Builder
usage
class _LogoAppState extends State<LogoApp> with TickerProviderStateMixin {
Animation animation;
AnimationController controller;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
final CurvedAnimation curve =
new CurvedAnimation(parent: controller, curve: Curves.bounceOut);
animation = new Tween(begin: 0.0, end: 300.0).animate(curve);
controller.forward();
}
Widget build(BuildContext context) {
return new GrowTransition(child: new LogoWidget(), animation: animation);
}
dispose() {
controller.dispose();
super.dispose(); }}class GrowTransition extends StatelessWidget {
GrowTransition({this.child, this.animation});
final Widget child;
final Animation<double> animation;
Widget build(BuildContext context) {
return new Center(
child: new AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
return newContainer( height: animation.value, width: animation.value, child: child); }, child: child), ); }}class LogoWidget extends StatelessWidget {
// Leave out the height and width so it fills the animating parent
build(BuildContext context) {
return new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
child: newFlutterLogo(), ); }}Copy the code
rendering
conclusion
This is the end of the Tween animation, Hero animation left in the supplement
Reference: The Flutter animation tutorial