Flutter Animation Basics
This section introduces animation implementation in Flutter, including implicit animations and explicit animations.
Implicit animation
Implicit animations are animation Widgets where Flutter automatically handles the transition effects.
Example: AnimatedContainer
class AnimatedContainerExample extends StatefulWidget {
const AnimatedContainerExample({super.key});
@override
State<AnimatedContainerExample> createState() =>
_AnimatedContainerExampleState();
}
class _AnimatedContainerExampleState
extends State<AnimatedContainerExample> {
bool _isLarge = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
// Click to toggle size
GestureDetector(
onTap: () => setState(() => _isLarge = !_isLarge),
child: AnimatedContainer(
duration: const Duration(milliseconds: 500), // Animation duration
curve: Curves.easeInOut, // Animation curve
width: _isLarge ? 200 : 100,
height: _isLarge ? 200 : 100,
color: _isLarge ? Colors.blue : Colors.red,
child: Center(
child: Text(
_isLarge ? 'large' : 'small',
style: const TextStyle(color: Colors.white, fontSize: 24),
),
),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => setState(() => _isLarge = !_isLarge),
child: const Text('Switch'),
),
],
);
}
}
const AnimatedContainerExample({super.key});
@override
State<AnimatedContainerExample> createState() =>
_AnimatedContainerExampleState();
}
class _AnimatedContainerExampleState
extends State<AnimatedContainerExample> {
bool _isLarge = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
// Click to toggle size
GestureDetector(
onTap: () => setState(() => _isLarge = !_isLarge),
child: AnimatedContainer(
duration: const Duration(milliseconds: 500), // Animation duration
curve: Curves.easeInOut, // Animation curve
width: _isLarge ? 200 : 100,
height: _isLarge ? 200 : 100,
color: _isLarge ? Colors.blue : Colors.red,
child: Center(
child: Text(
_isLarge ? 'large' : 'small',
style: const TextStyle(color: Colors.white, fontSize: 24),
),
),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => setState(() => _isLarge = !_isLarge),
child: const Text('Switch'),
),
],
);
}
}
Common Implicit Animation Widgets
| Widget | Description |
|---|---|
| AnimatedContainer | Animations when properties such as container size, color, etc., change |
| AnimatedOpacity | Opacity transition animation |
| AnimatedPadding | Padding transition animation |
| AnimatedAlign | Alignment Change Animation |
| AnimatedSwitcher | Animation when switching child widgets |
| AnimatedDefaultTextStyle | Text Style Change Animation |
Explicit animation
Explicit animations use AnimationController to fully control the animation.
Example: Rotation animation
class RotationAnimation extends StatefulWidget {
const RotationAnimation({super.key});
@override
State<RotationAnimation> createState() => _RotationAnimationState();
}
class _RotationAnimationState extends State<RotationAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
// Create animation controller
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
// Create rotation animation (0 to 1 maps to 0 to 2π)
_animation = Tween<double>(begin: 0, end: 6.28).animate(
CurvedAnimation(parent: _controller, curve: Curves.linear),
);
// Start animation
_controller.repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.rotate(
angle: _animation.value, // Rotation angle
child: const Icon(Icons.refresh, size: 100),
);
},
),
);
}
}
const RotationAnimation({super.key});
@override
State<RotationAnimation> createState() => _RotationAnimationState();
}
class _RotationAnimationState extends State<RotationAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
// Create animation controller
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
// Create rotation animation (0 to 1 maps to 0 to 2π)
_animation = Tween<double>(begin: 0, end: 6.28).animate(
CurvedAnimation(parent: _controller, curve: Curves.linear),
);
// Start animation
_controller.repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.rotate(
angle: _animation.value, // Rotation angle
child: const Icon(Icons.refresh, size: 100),
);
},
),
);
}
}
Staggered animation
Example: Staggered animation
class StaggeredAnimation extends StatefulWidget {
const StaggeredAnimation({super.key});
@override
State<StaggeredAnimation> createState() => _StaggeredAnimationState();
}
class _StaggeredAnimationState extends State<StaggeredAnimation>
with TickerProviderStateMixin {
late AnimationController _controller;
// Individual animations
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
// Fade-in animation (0-0.5)
_fadeAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0, 0.5, curve: Curves.easeOut),
),
);
// Scale Animation (0.3-0.7)
_scaleAnimation = Tween<double>(begin: 0.5, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.3, 0.7, curve: Curves.elasticOut),
),
);
// Slide-in Animation (0.5-1.0)
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 0.5),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.5, 1, curve: Curves.easeOutCubic),
),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return FadeTransition(
opacity: _fadeAnimation,
child: ScaleTransition(
scale: _scaleAnimation,
child: SlideTransition(
position: _slideAnimation,
child: const Card(
child: Padding(
padding: EdgeInsets.all(24),
child: Text('Welcome'),
),
),
),
),
);
},
);
}
}
const StaggeredAnimation({super.key});
@override
State<StaggeredAnimation> createState() => _StaggeredAnimationState();
}
class _StaggeredAnimationState extends State<StaggeredAnimation>
with TickerProviderStateMixin {
late AnimationController _controller;
// Individual animations
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
// Fade-in animation (0-0.5)
_fadeAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0, 0.5, curve: Curves.easeOut),
),
);
// Scale Animation (0.3-0.7)
_scaleAnimation = Tween<double>(begin: 0.5, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.3, 0.7, curve: Curves.elasticOut),
),
);
// Slide-in Animation (0.5-1.0)
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 0.5),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.5, 1, curve: Curves.easeOutCubic),
),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return FadeTransition(
opacity: _fadeAnimation,
child: ScaleTransition(
scale: _scaleAnimation,
child: SlideTransition(
position: _slideAnimation,
child: const Card(
child: Padding(
padding: EdgeInsets.all(24),
child: Text('Welcome'),
),
),
),
),
);
},
);
}
}
Hero animation
Hero animation is used to create shared element transition effects when switching between pages.
Example: Hero animation
// Home - image with Hero
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Center(
child: GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const DetailPage()),
),
child: Hero(
tag: 'myHero', // Same tag
child: Image.network(
'https://picsum.photos/200',
width: 200,
height: 200,
),
),
),
),
);
}
}
// Detail page - image with Hero
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Details')),
body: Center(
child: Hero(
tag: 'myHero', // Same tag
child: Image.network(
'https://picsum.photos/200',
width: 300,
height: 300,
),
),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Center(
child: GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const DetailPage()),
),
child: Hero(
tag: 'myHero', // Same tag
child: Image.network(
'https://picsum.photos/200',
width: 200,
height: 200,
),
),
),
),
);
}
}
// Detail page - image with Hero
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Details')),
body: Center(
child: Hero(
tag: 'myHero', // Same tag
child: Image.network(
'https://picsum.photos/200',
width: 300,
height: 300,
),
),
),
);
}
}
other extensionsImplicit animations suit simple scenarios, while explicit animations suit complex control.
Hero animation is the best choice for page transitions.