Animations add life and interactivity to your Flutter applications, enhancing the user experience. While Flutter makes it easy to create individual animations, combining multiple animations to play sequentially requires a bit more effort. This article demonstrates how to create sequential animations in Flutter, allowing you to build complex, captivating user interfaces.
What are Sequential Animations?
Sequential animations involve playing multiple animations one after the other. Instead of running concurrently, each animation waits for the previous one to complete before starting. This type of animation is ideal for guiding users through a sequence of actions or creating more elaborate visual effects.
Why Use Sequential Animations?
- Guided User Experiences: Direct users’ attention through a sequence of UI elements.
- Improved Interactivity: Add dynamism and polish to user interactions.
- Enhanced Visual Appeal: Create sophisticated and engaging effects.
How to Create Sequential Animations in Flutter
Flutter provides various tools and techniques for creating sequential animations. We’ll explore two primary approaches:
Method 1: Using Future.delayed
One straightforward way to chain animations is by using Future.delayed to introduce delays between each animation. This approach is simple and works well for basic sequential animations.
Step 1: Set up the Animation Controllers
Create AnimationController instances for each animation:
import 'package:flutter/material.dart';
class SequentialAnimationExample extends StatefulWidget {
@override
_SequentialAnimationExampleState createState() => _SequentialAnimationExampleState();
}
class _SequentialAnimationExampleState extends State
with TickerProviderStateMixin {
late AnimationController _controller1;
late AnimationController _controller2;
late AnimationController _controller3;
late Animation _animation1;
late Animation _animation2;
late Animation _animation3;
@override
void initState() {
super.initState();
_controller1 = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
_controller2 = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
_controller3 = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
_animation1 = Tween(begin: 0, end: 100).animate(_controller1);
_animation2 = Tween(begin: 0, end: 200).animate(_controller2);
_animation3 = Tween(begin: 0, end: 300).animate(_controller3);
}
@override
void dispose() {
_controller1.dispose();
_controller2.dispose();
_controller3.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sequential Animations'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedBuilder(
animation: _animation1,
builder: (context, child) => Text('Animation 1: ${_animation1.value.toStringAsFixed(0)}'),
),
AnimatedBuilder(
animation: _animation2,
builder: (context, child) => Text('Animation 2: ${_animation2.value.toStringAsFixed(0)}'),
),
AnimatedBuilder(
animation: _animation3,
builder: (context, child) => Text('Animation 3: ${_animation3.value.toStringAsFixed(0)}'),
),
ElevatedButton(
onPressed: () {
_startAnimations();
},
child: const Text('Start Animations'),
),
],
),
),
);
}
Future _startAnimations() async {
await _controller1.forward().orCancel;
await Future.delayed(const Duration(milliseconds: 500)); // Small delay
await _controller2.forward().orCancel;
await Future.delayed(const Duration(milliseconds: 500)); // Small delay
await _controller3.forward().orCancel;
}
}
Step 2: Start the Animations Sequentially
Use Future.delayed to create the sequence:
Future _startAnimations() async {
await _controller1.forward().orCancel;
await Future.delayed(const Duration(milliseconds: 500)); // Small delay
await _controller2.forward().orCancel;
await Future.delayed(const Duration(milliseconds: 500)); // Small delay
await _controller3.forward().orCancel;
}
This method simply waits for each animation to complete and introduces a delay before starting the next one.
Method 2: Using AnimationController Listeners
A more robust approach involves using listeners attached to the AnimationController. This method is more precise and avoids potential issues with timing.
Step 1: Implement the AnimationController Listeners
Add listeners to the AnimationController to start the next animation when the previous one completes:
import 'package:flutter/material.dart';
class SequentialAnimationExample extends StatefulWidget {
@override
_SequentialAnimationExampleState createState() => _SequentialAnimationExampleState();
}
class _SequentialAnimationExampleState extends State
with TickerProviderStateMixin {
late AnimationController _controller1;
late AnimationController _controller2;
late AnimationController _controller3;
late Animation _animation1;
late Animation _animation2;
late Animation _animation3;
@override
void initState() {
super.initState();
_controller1 = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller2.forward();
}
});
_controller2 = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller3.forward();
}
});
_controller3 = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
_animation1 = Tween(begin: 0, end: 100).animate(_controller1);
_animation2 = Tween(begin: 0, end: 200).animate(_controller2);
_animation3 = Tween(begin: 0, end: 300).animate(_controller3);
}
@override
void dispose() {
_controller1.dispose();
_controller2.dispose();
_controller3.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sequential Animations'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedBuilder(
animation: _animation1,
builder: (context, child) => Text('Animation 1: ${_animation1.value.toStringAsFixed(0)}'),
),
AnimatedBuilder(
animation: _animation2,
builder: (context, child) => Text('Animation 2: ${_animation2.value.toStringAsFixed(0)}'),
),
AnimatedBuilder(
animation: _animation3,
builder: (context, child) => Text('Animation 3: ${_animation3.value.toStringAsFixed(0)}'),
),
ElevatedButton(
onPressed: () {
_startAnimations();
},
child: const Text('Start Animations'),
),
],
),
),
);
}
void _startAnimations() {
_controller1.forward();
}
}
Here’s what’s happening in this code:
- Three AnimationController instances (_controller1, _controller2, _controller3) are created to manage the animations.
- Each controller is linked to a corresponding Animation object that defines the transition from a beginning to an end value over a specified duration.
- The
initStatemethod initializes the animation controllers and adds listeners. Each listener is set to call theforward()method on the next animation controller when the previous animation completes. - Each AnimationController has a StatusListener attached that triggers the next animation upon completion of the current one.
Step 2: Trigger the First Animation
Only trigger the first animation using the following line:
void _startAnimations() {
_controller1.forward();
}
Tips for Working with Sequential Animations
- Use Curves: Experiment with different
Curvevalues to make your animations more engaging. - Consider Animation Duration: Fine-tune the duration of each animation to achieve the desired effect.
- Handle State Changes: Be mindful of how state changes affect your animations, especially when using
setState. - Test Thoroughly: Ensure your animations work correctly across different devices and screen sizes.
Conclusion
Sequential animations can greatly enhance the interactivity and visual appeal of your Flutter applications. By using Future.delayed or AnimationController listeners, you can create complex animations that play one after another. Experiment with these techniques to create engaging user experiences.