If tapping a button only needs to change its size and color smoothly, starting with a controller adds code. If the interaction needs precise progress control, forcing it into AnimatedContainer can make that control awkward. Choose according to how much control over time the animation needs.
Use an implicit animation for a simple value change
AnimatedContainer interpolates between the configured property values. If you need to pause or reverse progress on demand, AnimationController provides direct control over the timeline.
class SizeButton extends StatefulWidget {
const SizeButton({super.key});
@override
State<SizeButton> createState() => _SizeButtonState();
}
class _SizeButtonState extends State<SizeButton> {
bool selected = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => setState(() => selected = !selected),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: selected ? 160 : 120,
height: 48,
color: selected ? Colors.blue : Colors.grey,
),
);
}
}Place SizeButton on a screen and tap it: width and color change over 200 milliseconds. Tapping again returns to the original values. This is readable when the endpoints are clear and you do not need to pause midway or synchronize several effects. The visible tap area changes, so a production control also needs an accessible name and adequate touch target.
Use a controller when you need the timeline
Use AnimationController when progress follows a drag, multiple effects must be coordinated precisely, or you need repeat, pause, and reverse controls. The owning State must call dispose() on the controller; otherwise its ticker can outlive the screen.
First test the implicit example with several quick taps and confirm that its final appearance matches selected. Switch to a controller when a real requirement calls for stopping or reversing progress from outside the value change. After that switch, close and reopen the screen to check for animation lifecycle errors.
Motion should help, not obstruct
Animation can hinder use if it keeps moving an important target or delays an action. Keep changes brief and predictable, and consider reduced-motion accessibility settings. If performance suffers, check for heavy work on each frame or unnecessarily rebuilding a large widget tree.
Key takeaways
Use an implicit animation for straightforward property changes. Use an AnimationController when you need fine control of the timeline, and always dispose of it. Let the required interaction, not a preference for more elaborate code, set the animation's complexity.

