Let’s be honest: a static app feels like a legacy app. In my experience building production-ready mobile interfaces, the difference between a ‘good’ app and a ‘premium’ app usually comes down to motion. Whether it’s a subtle button bounce or a complex page transition, animations provide the tactile feedback users crave.
In this flutter animations tutorial for mobile, I’m going to strip away the academic jargon and show you how to actually implement motion that enhances UX without tanking your frame rate. We’ll cover the two main pillars of Flutter motion: Implicit and Explicit animations.
Prerequisites
Before we dive into the code, make sure you have the following set up in your environment:
- Flutter SDK installed and configured (Stable channel).
- A basic understanding of State Management (setState is fine for this tutorial).
- An IDE of your choice (I personally use VS Code with the Flutter/Dart extensions).
- A physical device or emulator for testing (animations can sometimes look different on a simulator vs. real hardware).
Step 1: Mastering Implicit Animations
Implicit animations are the ‘easy win’ of Flutter. They are essentially widgets that automatically animate when their properties change. You don’t need a controller; you just change a value and call setState().
The most common implicit widgets are AnimatedContainer, AnimatedOpacity, and AnimatedAlign. Here is a practical example of an AnimatedContainer that changes size and color based on a user tap:
class ImplicitDemo extends StatefulWidget {
@override
_ImplicitDemoState createState() => _ImplicitDemoState();
}
class _ImplicitDemoState extends State<ImplicitDemo> {
bool _expanded = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => setState(() => _expanded = !_expanded),
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: _expanded ? 200.0 : 100.0,
height: _expanded ? 200.0 : 100.0,
color: _expanded ? Colors.blue : Colors.red,
child: Center(child: Text(_expanded ? "Expanded!" : "Tap Me")),
),
);
}
}
Notice how we didn’t have to write a single line of logic to handle the transition between 100px and 200px. Flutter handles the interpolation for us. If you’re looking to scale this across a larger app, I recommend checking out my guide on flutter responsive ui best practices to ensure your animations look great on all screen sizes.
Step 2: Taking Control with Explicit Animations
Implicit animations are great, but what if you want an animation to loop infinitely? Or what if you need to reverse it precisely when a specific event occurs? That’s where Explicit animations come in. These require an AnimationController.
To use an AnimationController, your widget must use a TickerProviderStateMixin. Here is how I typically implement a rotating loading icon or a pulsing button:
class ExplicitDemo extends StatefulWidget {
@override
_ExplicitDemoState createState() => _ExplicitDemoState();
}
class _ExplicitDemoState extends State<ExplicitDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
_animation = Tween<double>(begin: 1.0, end: 1.2).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_controller.dispose(); // Crucial to avoid memory leaks!
super.dispose();
}
@override
Widget build(BuildContext context) {
return ScaleTransition(
scale: _animation,
child: FlutterLogo(size: 100),
);
}
}
As shown in the image below, the key difference here is the manual lifecycle management. You must initialize the controller and, more importantly, dispose of it to prevent memory leaks in your mobile application.
Pro Tips for High-Performance Motion
- Avoid rebuilding the whole tree: Use
AnimatedBuilderor specialized transition widgets (likeScaleTransitionorRotationTransition) to ensure only the animating widget rebuilds, not the entire page. - Prefer Bezier Curves: Never use
Curves.linearfor UI elements. It feels robotic. UseCurves.easeOutExpoorCurves.fastOutSlowInfor a more natural, organic feel. - Hardware Acceleration: Keep an eye on the Flutter DevTools performance overlay. If you see red bars (jank), you might be animating properties that trigger expensive layout passes (like changing a font size) instead of visual transforms (like scaling).
Troubleshooting Common Animation Issues
Problem: My animation is stuttering (Jank).
I’ve found that this is usually caused by doing too much work in the build() method. Ensure you aren’t performing heavy logic or API calls inside a widget that is animating 60 times per second.
Problem: Memory leaks after navigating away.
This happens if you forget to call _controller.dispose(). Always override the dispose() method in your State class when using explicit animations.
What’s Next?
Once you’ve mastered basic transitions, I suggest exploring Rive for complex vector animations or Hero animations for seamless page transitions. If your app involves location-based services, you might want to learn how to integrate google maps in flutter guide and then animate the camera movement for a smoother user experience.