Flutter - Animation Examples II
Information drawn from
Simplifying with AnimatedWidget
Demo
What’s the point?
- How to use the AnimatedWidget helper class (instead of addListener() and setState()) to create a widget that animates.
- Use AnimatedWidget to create a widget that performs a reusable animation. To separate the transition from the widget, use an AnimatedBuilder, as shown in the Refactoring with AnimatedBuilder section.
- Examples of AnimatedWidgets in the Flutter API: AnimatedBuilder, AnimatedModalBarrier, DecoratedBoxTransition, FadeTransition, PositionedTransition, RelativePositionedTransition, RotationTransition, ScaleTransition, SizeTransition, SlideTransition.
The AnimatedWidget base class allows you to separate out the core widget code from the animation code. AnimatedWidget doesn’t need to maintain a State object to hold the animation. Add the following AnimatedLogo class:
lib/main.dart (AnimatedLogo)
class AnimatedLogo extends AnimatedWidget {
const AnimatedLogo({Key? key, required Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: animation.value,
width: animation.value,
child: const FlutterLogo(),
),
);
}
}
AnimatedLogo uses the current value of the animation when drawing itself.
The LogoApp still manages the AnimationController and the Tween, and it passes the Animation object to AnimatedLogo:
{animate1 → animate2}/lib/main.dart
Viewed
@@ -1,10 +1,28 @@
11 import 'package:flutter/material.dart';
22 void main() => runApp(const LogoApp());
3 + class AnimatedLogo extends AnimatedWidget {
4 + const AnimatedLogo({Key? key, required Animation<double> animation})
5 + : super(key: key, listenable: animation);
6 +
7 + @override
8 + Widget build(BuildContext context) {
9 + final animation = listenable as Animation<double>;
10 + return Center(
11 + child: Container(
12 + margin: const EdgeInsets.symmetric(vertical: 10),
13 + height: animation.value,
14 + width: animation.value,
15 + child: const FlutterLogo(),
16 + ),
17 + );
18 + }
19 + }
20 +
321 class LogoApp extends StatefulWidget {
422 const LogoApp({Key? key}) : super(key: key);
523 @override
624 _LogoAppState createState() => _LogoAppState();
725 }
@@ -15,32 +33,18 @@
1533 @override
1634 void initState() {
1735 super.initState();
1836 controller =
1937 AnimationController(duration: const Duration(seconds: 2), vsync: this);
20 - animation = Tween<double>(begin: 0, end: 300).animate(controller)
21 - ..addListener(() {
22 - setState(() {
23 - // The state that has changed here is the animation object’s value.
24 - });
25 - });
38 + animation = Tween<double>(begin: 0, end: 300).animate(controller);
2639 controller.forward();
2740 }
2841 @override
29 - Widget build(BuildContext context) {
30 - return Center(
31 - child: Container(
32 - margin: const EdgeInsets.symmetric(vertical: 10),
33 - height: animation.value,
34 - width: animation.value,
35 - child: const FlutterLogo(),
36 - ),
37 - );
38 - }
42 + Widget build(BuildContext context) => AnimatedLogo(animation: animation);
3943 @override
4044 void dispose() {
4145 controller.dispose();
4246 super.dispose();
4347 }
App source: animate2
Monitoring the progress of the animation
What’s the point?
- Use addStatusListener() for notifications of changes to the animation’s state, such as starting, stopping, or reversing direction.
- Run an animation in an infinite loop by reversing direction when the animation has either completed or returned to its starting state.
It’s often helpful to know when an animation changes state, such as finishing, moving forward, or reversing. You can get notifications for this with addStatusListener(). The following code modifies the previous example so that it listens for a state change and prints an update. The highlighted line shows the change:
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
void initState() {
super.initState();
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = Tween<double>(begin: 0, end: 300).animate(controller)
..addStatusListener((state) => print('$state'));
controller.forward();
}
// ...
}
Running this code produces this output:
AnimationStatus.forward
AnimationStatus.completed
Next, use addStatusListener() to reverse the animation at the beginning or the end. This creates a “breathing” effect:
{animate2 → animate3}/lib/main.dart
Viewed
@@ -35,7 +35,15 @@
3535 void initState() {
3636 super.initState();
3737 controller =
3838 AnimationController(duration: const Duration(seconds: 2), vsync: this);
39 - animation = Tween<double>(begin: 0, end: 300).animate(controller);
39 + animation = Tween<double>(begin: 0, end: 300).animate(controller)
40 + ..addStatusListener((status) {
41 + if (status == AnimationStatus.completed) {
42 + controller.reverse();
43 + } else if (status == AnimationStatus.dismissed) {
44 + controller.forward();
45 + }
46 + })
47 + ..addStatusListener((state) => print('$state'));
4048 controller.forward();
4149 }
App source: animate3
------------------------------------------------------------------------
Last update on 30 Jan 2022
---