flutter_animate: Say Goodbye to AnimationController Boilerplate
How one package changed the way I write Flutter animations
flutter_animate: Say Goodbye to AnimationController Boilerplate
I spent 30 minutes writing a fade-in animation last week. Not because the effect was complex—just a simple opacity transition. But by the time I set up the AnimationController, mixed in SingleTickerProviderStateMixin, created the Tween, and remembered to dispose(), I questioned my life choices.
Then I discovered flutter_animate. Now that same animation is one line. Let me show you.
Table of Contents
The Problem: Animation Boilerplate
Here's what a simple fade-in looks like with vanilla Flutter:
// Before: ~30 lines for a fade-in
class FadeInWidget extends StatefulWidget {
@override
_FadeInWidgetState createState() => _FadeInWidgetState();
}
class _FadeInWidgetState extends State<FadeInWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 500),
vsync: this,
);
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animation,
child: Text("Hello World!"),
);
}
}
Now with flutter_animate:
// After: 1 line
Text("Hello World!").animate().fadeIn(duration: 500.ms)
That's it. One line. No StatefulWidget, no dispose(), no boilerplate.
What is flutter_animate?
Package Info
pub.dev: flutter_animate
GitHub: gskinner/flutter_animate
Publisher: gskinner.com (verified)
Version: 4.5.2 | License: BSD-3-Clause
flutter_animate is a package by gskinner.com that provides a unified API for adding animations to any widget. It has:
4k+ likes and 524k+ downloads on pub.dev
Pre-built effects: fade, scale, slide, blur, shake, shimmer, flip, and more
Chaining syntax: combine effects with a fluent API
Zero AnimationController management: it handles all the lifecycle for you
The core idea? Any widget can be animated by adding .animate().
Getting Started
Install the package:
# pubspec.yaml
dependencies:
flutter_animate: ^4.5.2
Import it:
import 'package:flutter_animate/flutter_animate.dart';
That's all the setup you need.
Basic Usage: The Chaining Magic
Single Effect
// Fade in
Text("Hello").animate().fadeIn()
// Scale up
Icon(Icons.star).animate().scale()
// Slide from left
Card(child: content).animate().slideX(begin: -1)
Combining Effects (Simultaneous)
When effects share the same timing, they run together:
// Fade AND scale at the same time
Text("Hello")
.animate()
.fadeIn(duration: 600.ms)
.scale(begin: Offset(0.8, 0.8))
Two Syntax Options
You can also use the declarative style if you prefer:
// Declarative syntax
Animate(
effects: [
FadeEffect(duration: 600.ms),
ScaleEffect(begin: Offset(0.8, 0.8)),
],
child: Text("Hello"),
)
I personally prefer the chaining syntax—it reads more naturally.
Timing Control
Duration Helpers
The package adds extension methods to num for cleaner duration syntax:
// These are equivalent
Duration(milliseconds: 500) // Vanilla Flutter
500.ms // flutter_animate
// More examples
2.seconds
0.1.minutes
Sequential Animations with then()
Use then() to run effects in sequence:
Text("Hello")
.animate()
.fadeIn(duration: 300.ms)
.then(delay: 100.ms) // Wait 100ms after fade completes
.slideY(begin: 0.2, end: 0) // Then slide up
Property Inheritance
Here's something clever: if you don't specify a property, it inherits from the previous effect.
Text("Hello")
.animate()
.fadeIn(duration: 500.ms, curve: Curves.easeOut)
.slideY() // Inherits duration: 500.ms, curve: Curves.easeOut
This keeps your code DRY when multiple effects share timing.
Real-World Examples
Example 1: Login Screen Entrance
Elements appearing one after another:
class LoginScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo: fade + scale
FlutterLogo(size: 80)
.animate()
.fadeIn(duration: 600.ms)
.scale(begin: Offset(0.8, 0.8)),
SizedBox(height: 48),
// Email: slide from left
TextField(decoration: InputDecoration(labelText: "Email"))
.animate()
.fadeIn(delay: 200.ms, duration: 400.ms)
.slideX(begin: -0.2),
SizedBox(height: 16),
// Password: slide from right
TextField(decoration: InputDecoration(labelText: "Password"))
.animate()
.fadeIn(delay: 400.ms, duration: 400.ms)
.slideX(begin: 0.2),
SizedBox(height: 32),
// Button: slide up
ElevatedButton(onPressed: () {}, child: Text("Login"))
.animate()
.fadeIn(delay: 600.ms, duration: 400.ms)
.slideY(begin: 0.3),
],
),
),
);
}
}
Example 2: List Stagger Effect
Use interval to stagger animations across list items:
Column(
children: [
Text("Item 1"),
Text("Item 2"),
Text("Item 3"),
].animate(interval: 200.ms) // 200ms between each
.fadeIn(duration: 300.ms)
.slideX(begin: -0.1),
)
Each item animates 200ms after the previous one. No manual delay calculations needed.
Example 3: Hover Effect with target
For state-driven animations, use the target parameter:
class HoverButton extends StatefulWidget {
@override
_HoverButtonState createState() => _HoverButtonState();
}
class _HoverButtonState extends State<HoverButton> {
bool _isHovered = false;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => setState(() => _isHovered = true),
onExit: (_) => setState(() => _isHovered = false),
child: ElevatedButton(
onPressed: () {},
child: Text("Hover Me"),
)
.animate(target: _isHovered ? 1 : 0)
.scaleXY(end: 1.1)
.fade(end: 0.8),
);
}
}
When target changes, the animation smoothly transitions to the new state.
Pro Tips
1. Looping Animations
Use onPlay to repeat:
Icon(Icons.notifications)
.animate(onPlay: (controller) => controller.repeat(reverse: true))
.scale(end: Offset(1.2, 1.2), duration: 500.ms)
2. Shimmer for Loading States
Perfect for skeleton screens:
Container(
width: 200,
height: 20,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(4),
),
)
.animate(onPlay: (c) => c.repeat())
.shimmer(duration: 1200.ms)
3. Custom Effects
When built-in effects aren't enough:
Container()
.animate()
.custom(
duration: 300.ms,
builder: (context, value, child) {
// value goes from 0.0 to 1.0
return Transform.rotate(
angle: value * 2 * 3.14159, // Full rotation
child: child,
);
},
)
4. Scroll-Based Animations
Use ScrollAdapter to tie animations to scroll position:
ListView(
controller: scrollController,
children: items.map((item) =>
ItemCard(item)
.animate(adapter: ScrollAdapter(scrollController))
.fadeIn()
.slideX(begin: 0.2)
).toList(),
)
When NOT to Use
Large Lists
Avoid complex animations on lists with hundreds of items:
// Potentially problematic
ListView.builder(
itemCount: 1000,
itemBuilder: (_, i) => Item()
.animate()
.fadeIn()
.scale()
.blur()
.shimmer() // Too many effects
)
For large lists, consider animating only visible items or using simpler effects.
When You Need Fine-Grained Control
If you need to:
Sync multiple animations to a single controller
Build complex physics-based animations
Implement gesture-driven animations with velocity
...you might still want the native AnimationController approach.
Alternatives to Consider
| Use Case | Consider |
| Simple entry effects | animate_do (zero dependencies) |
| Material motion | animations (official package) |
| Complex timelines | Native AnimationController |
Wrapping Up
flutter_animate removes the friction from Flutter animations. Instead of wrestling with controllers and state management, you can focus on what matters: making your UI feel alive.
Key takeaways:
.animate()turns any widget into an animated widgetChain effects for simultaneous animations
Use
then()for sequential animationsUse
intervalfor list stagger effectsUse
targetfor state-driven animations
Next time you think "I should add a subtle animation here," you no longer have an excuse to skip it. It's literally one line.
That's it for Flutter Packages Weekly #1! Next week, I'll introduce another package that might change how you work.
Questions or suggestions? Drop a comment below.
— woogi
Sources & References:
| Resource | Link |
| flutter_animate (pub.dev) | https://pub.dev/packages/flutter_animate |
| GitHub Repository | https://github.com/gskinner/flutter_animate |
| gskinner.com (Publisher) | https://gskinner.com |
| Flutter Animation Docs | https://docs.flutter.dev/ui/animations |
| animate_do (Alternative) | https://pub.dev/packages/animate_do |
| animations (Official) | https://pub.dev/packages/animations |