We've explored the building blocks of animations in Flutter: tweens, curves, and controllers. Now, let's synthesize these concepts into a more realistic scenario. Imagine an app that displays a list of items, and when a user taps an item, it expands to reveal more details, fading in and scaling up simultaneously. This involves coordinating multiple animation properties and managing their lifecycle.
To achieve this, we'll need a StatefulWidget. This allows us to manage the animation state, including the AnimationController and the AnimatedWidget (or AnimatedBuilder) that will use the animation values.
class ExpandableListItem extends StatefulWidget {
const ExpandableListItem({Key? key}) : super(key: key);
@override
_ExpandableListItemState createState() => _ExpandableListItemState();
}
class _ExpandableListItemState extends State<ExpandableListItem> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _opacityAnimation;
bool _isExpanded = false;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
_scaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_opacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeIn)
);
_controller.addListener(() {
setState(() {});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _toggleExpand() {
setState(() {
_isExpanded = !_isExpanded;
if (_isExpanded) {
_controller.forward();
} else {
_controller.reverse();
}
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _toggleExpand,
child: ScaleTransition(
scale: _scaleAnimation,
child: Opacity(
opacity: _opacityAnimation.value,
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Item Title', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
if (_isExpanded) ...[
SizedBox(height: 10),
Text('Detailed information about this item goes here.'),
]
],In this example, we're using SingleTickerProviderStateMixin for our AnimationController. We define two animations: one for scaling and another for opacity. When the user taps the GestureDetector, we toggle the _isExpanded state and then either forward or reverse the _controller. The _controller.addListener ensures that setState is called whenever the animation value changes, triggering a rebuild of the widget.