Flutter’s animation framework is a powerful tool for creating delightful and engaging user experiences. Among its components, the AnimationController stands out as a cornerstone for managing animations explicitly. Understanding and mastering AnimationController is essential for developers looking to build custom, complex animations in their Flutter apps.
What is AnimationController in Flutter?
The AnimationController is a special class in Flutter that manages an animation. It lets you control the animation’s duration, start, stop, and reverse it. Essentially, it’s the conductor of your animation orchestra.
Why Use AnimationController?
- Fine-Grained Control: Provides explicit control over the animation’s behavior.
- Custom Animations: Allows creating complex, bespoke animations tailored to specific needs.
- Performance: Optimizes animation performance through efficient management of animation values.
How to Implement Explicit Animations with AnimationController in Flutter
To implement explicit animations with AnimationController, follow these steps:
Step 1: Set Up the AnimationController
First, you need to create and initialize an AnimationController. It’s typically done within the State of a StatefulWidget.
import 'package:flutter/material.dart';
class MyAnimatedWidget extends StatefulWidget {
@override
_MyAnimatedWidgetState createState() => _MyAnimatedWidgetState();
}
class _MyAnimatedWidgetState extends State<MyAnimatedWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(); // Placeholder
}
}
In this setup:
- We extend the state with
SingleTickerProviderStateMixinto provide avsync(vertical synchronization) for the animation, which helps to optimize performance. - The
AnimationControlleris initialized with adurationand avsync. - The
disposemethod is overridden to release the resources held by theAnimationControllerwhen the widget is removed from the tree.
Step 2: Create an Animation
Next, create an Animation object that uses the AnimationController. The Animation object defines the range of values that will be animated.
import 'package:flutter/material.dart';
class MyAnimatedWidget extends StatefulWidget {
@override
_MyAnimatedWidgetState createState() => _MyAnimatedWidgetState();
}
class _MyAnimatedWidgetState extends State<MyAnimatedWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_animation = Tween(begin: 0, end: 300).animate(_controller);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Container(
height: _animation.value,
width: _animation.value,
color: Colors.blue,
);
},
);
}
}
Here:
Tweencreates an animation that interpolates between 0 and 300.(begin: 0, end: 300).animate(_controller) - The
AnimatedBuilderrebuilds the UI whenever the animation’s value changes. - The height and width of the
Containerare animated based on the current value of_animation.
Step 3: Control the Animation
Control the animation using methods like forward(), reverse(), repeat(), and animateTo().
import 'package:flutter/material.dart';
class MyAnimatedWidget extends StatefulWidget {
@override
_MyAnimatedWidgetState createState() => _MyAnimatedWidgetState();
}
class _MyAnimatedWidgetState extends State<MyAnimatedWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_animation = Tween(begin: 0, end: 300).animate(_controller);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _startAnimation() {
_controller.forward();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _startAnimation,
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Container(
height: _animation.value,
width: _animation.value,
color: Colors.blue,
);
},
),
);
}
}
In this extended example:
- A
GestureDetectorwraps the animated container to detect taps. - The
_startAnimationmethod starts the animation when the container is tapped.
Step 4: Add Looping or Repeating Animations
To create a looping animation, use the repeat method:
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_animation = Tween(begin: 0, end: 300).animate(_controller)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
_controller.forward();
}
In this case, we’ve added a status listener to the animation that reverses the animation when it completes and forwards it when it’s dismissed, creating a looping effect.
Examples of Explicit Animations
Example 1: Fade Transition
Use AnimationController to control a fade transition:
import 'package:flutter/material.dart';
class FadeAnimation extends StatefulWidget {
final Widget child;
final Duration duration;
FadeAnimation({required this.child, required this.duration});
@override
_FadeAnimationState createState() => _FadeAnimationState();
}
class _FadeAnimationState extends State<FadeAnimation> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(duration: widget.duration, vsync: this);
_animation = Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeIn),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animation,
child: widget.child,
);
}
}
// Usage:
FadeAnimation(
duration: Duration(seconds: 1),
child: Text('Fade In Text'),
)
Example 2: Size Transition
Animate the size of a widget:
import 'package:flutter/material.dart';
class SizeAnimation extends StatefulWidget {
final Widget child;
final Duration duration;
final double endSize;
SizeAnimation({required this.child, required this.duration, required this.endSize});
@override
_SizeAnimationState createState() => _SizeAnimationState();
}
class _SizeAnimationState extends State<SizeAnimation> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(duration: widget.duration, vsync: this);
_animation = Tween(begin: 0.0, end: widget.endSize).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return SizedBox(
width: _animation.value,
height: _animation.value,
child: widget.child,
);
},
);
}
}
// Usage:
SizeAnimation(
duration: Duration(seconds: 1),
endSize: 200.0,
child: Container(color: Colors.red),
)
Conclusion
Mastering explicit animations with AnimationController in Flutter empowers developers to create highly customized and performant animations. By understanding the role and functions of AnimationController, you can unlock a world of possibilities for enhancing the user experience in your Flutter applications. Whether it’s creating complex transitions or engaging visual effects, AnimationController is your key to crafting captivating Flutter animations.