1. Implicit Animations
class AnimatedCard extends StatefulWidget {
@override
State<AnimatedCard> createState() => _AnimatedCardState();
}
class _AnimatedCardState extends State<AnimatedCard> {
bool _expanded = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => setState(() => _expanded = !_expanded),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: _expanded ? 300 : 200,
height: _expanded ? 200 : 100,
decoration: BoxDecoration(
color: _expanded ? Colors.indigo : Colors.grey[300],
borderRadius: BorderRadius.circular(_expanded ? 24 : 12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(_expanded ? 0.3 : 0.1),
blurRadius: _expanded ? 16 : 4,
offset: Offset(0, _expanded ? 8 : 2),
),
],
),
child: AnimatedOpacity(
opacity: _expanded ? 1.0 : 0.6,
duration: const Duration(milliseconds: 300),
child: Center(
child: AnimatedDefaultTextStyle(
style: TextStyle(
fontSize: _expanded ? 24 : 16,
color: _expanded ? Colors.white : Colors.black,
),
duration: const Duration(milliseconds: 300),
child: const Text('Tap me'),
),
),
),
),
);
}
}
2. Explicit Animations
class PulseAnimation extends StatefulWidget {
@override
State<PulseAnimation> createState() => _PulseAnimationState();
}
class _PulseAnimationState extends State<PulseAnimation>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _scaleAnimation;
late final Animation<double> _opacityAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat(reverse: true);
_scaleAnimation = Tween<double>(begin: 0.8, end: 1.2).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
_opacityAnimation = Tween<double>(begin: 0.5, end: 1.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeIn),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Opacity(
opacity: _opacityAnimation.value,
child: Transform.scale(
scale: _scaleAnimation.value,
child: child,
),
);
},
child: const Icon(Icons.favorite, size: 80, color: Colors.red),
);
}
}
3. Hero & Page Transitions
// Source page
Hero(
tag: 'product-${product.id}',
child: Image.network(product.imageUrl, width: 120, height: 120, fit: BoxFit.cover),
);
// Detail page
Hero(
tag: 'product-${product.id}',
child: Image.network(product.imageUrl, width: double.infinity, height: 300, fit: BoxFit.cover),
);
// Custom page transition
GoRoute(
path: '/detail/:id',
pageBuilder: (context, state) => CustomTransitionPage(
child: DetailPage(id: state.pathParameters['id']!),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: CurvedAnimation(parent: animation, curve: Curves.easeIn),
child: SlideTransition(
position: Tween<Offset>(begin: const Offset(0, 0.1), end: Offset.zero)
.animate(CurvedAnimation(parent: animation, curve: Curves.easeOut)),
child: child,
),
);
},
),
);
4. Lottie Animations
// pubspec.yaml: lottie: ^3.1.0
Lottie.asset(
'assets/animations/loading.json',
width: 200,
height: 200,
repeat: true,
animate: true,
);
// Controller-based
class LottieExample extends StatefulWidget {
@override
State<LottieExample> createState() => _LottieExampleState();
}
class _LottieExampleState extends State<LottieExample>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
_controller.forward(from: 0);
},
child: Lottie.asset(
'assets/animations/like.json',
controller: _controller,
onLoaded: (composition) {
_controller.duration = composition.duration;
},
),
);
}
}
Next article: Custom Painting & Advanced Widgets.