useAnimationController
Creates an AnimationController whose ticker provider and disposal are managed for you. It returns the controller directly, ready to drive transitions.
class FadeInLogo extends HookWidget {
const FadeInLogo({super.key});
Widget build(BuildContext context) {
// Ticker provider and disposal are handled for you.
final controller = useAnimationController(duration: const Duration(milliseconds: 300));
useEffect(() {
controller.forward(); // <- Effects run after the build, so this is safe
return null;
}, const []);
return FadeTransition(
opacity: controller, // <- AnimationController is itself an Animation<double>
child: const FlutterLogo(size: 96),
);
}
}
Signature
AnimationController useAnimationController({
Duration? duration,
Duration? reverseDuration,
String? debugLabel,
double initialValue = 0,
double lowerBound = 0,
double upperBound = 1,
TickerProvider? vsync,
AnimationBehavior animationBehavior = AnimationBehavior.normal,
});
The parameters mirror the AnimationController constructor. When vsync is omitted, the hook calls useSingleTickerProvider for you, so a single controller works with no extra setup. duration and reverseDuration are watched: changing them on a later build updates the live controller rather than recreating it; changing vsync re-syncs it.
Use cases
-
Any widget that owns an animation and would otherwise need a
StatefulWidgetwithSingleTickerProviderStateMixinand adisposeoverride. -
Staggered animations - the
staggeredextension onAnimationControllermaps oneTweenonto a sub-interval[start, end]of the controller, so several properties animate from one controller:class StaggeredCard extends HookWidget {const StaggeredCard({super.key});Widget build(BuildContext context) {final controller = useAnimationController(duration: const Duration(milliseconds: 400));// Each staggered() maps one tween onto a sub-interval of the same controller.final fade = controller.staggered(tween: Tween(begin: 0.0, end: 1.0), start: 0.0, end: 0.6);final slide = controller.staggered(tween: Tween(begin: const Offset(0, 0.1), end: Offset.zero),start: 0.3,end: 1.0,curve: Curves.easeOut,);useEffect(() {controller.forward();return null;}, const []);return FadeTransition(opacity: fade,child: SlideTransition(position: slide, child: const Card(child: SizedBox(height: 120))),);}}
Caveats
-
Don't call
forward(),repeat(), oranimateTo()during the build. Like writing auseState, starting the animation in the build body fights the framework - drive it from an effect or a callback.final controller = useAnimationController(duration: const Duration(milliseconds: 300));// DON'Tcontroller.forward(); // <- Mutating during build// DOuseEffect(() {controller.forward(); // <- Effects run after the buildreturn null;}, const []); -
This is a thin wrapper over
useMemoized(AnimationController.new, [], dispose)plus an automaticvsync. You could write thatuseMemoizedby hand, but the dedicated hook also forwards every constructor argument, re-syncs the ticker, and pushesdurationchanges onto the live controller - prefer it over rolling your own. -
For more than one controller in the same widget, pass an explicit
vsyncfrom a singleTickerProviderStateMixinsource, or accept that eachuseAnimationControllerprovisions its own single ticker. The default ticker is single, matchingSingleTickerProviderStateMixin.
See also
- useSingleTickerProvider - the ticker this hook provisions when
vsyncis omitted - useEffect - where to start the animation after the build
- useMemoized - the lower-level building block this hook wraps