DEV Community

kanta13jp1
kanta13jp1

Posted on

Flutter Animations Guide — AnimationController, Hero, Implicit, and Rive

Flutter Animations Guide — AnimationController, Hero, Implicit, and Rive

Animations are the glue of great UX. Done right they feel natural; done wrong they tank performance. Here's a complete breakdown of Flutter's animation system.

AnimationController + Tween

Most flexible. Use for custom, orchestrated animations.

class FadeInWidget extends StatefulWidget {
  final Widget child;
  const FadeInWidget({required this.child});
  @override
  State<FadeInWidget> createState() => _State();
}

class _State extends State<FadeInWidget> with SingleTickerProviderStateMixin {
  late final AnimationController _ctrl;
  late final Animation<double> _opacity;

  @override
  void initState() {
    super.initState();
    _ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 600));
    _opacity = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _ctrl, curve: Curves.easeOut),
    );
    _ctrl.forward();
  }

  @override
  void dispose() { _ctrl.dispose(); super.dispose(); }

  @override
  Widget build(BuildContext context) => FadeTransition(opacity: _opacity, child: widget.child);
}
Enter fullscreen mode Exit fullscreen mode

Staggered Animations

_slide = Tween(begin: const Offset(0, 0.3), end: Offset.zero).animate(
  CurvedAnimation(parent: _ctrl, curve: const Interval(0.0, 0.6, curve: Curves.easeOut)),
);
_opacity = Tween(begin: 0.0, end: 1.0).animate(
  CurvedAnimation(parent: _ctrl, curve: const Interval(0.0, 0.5)),
);
_scale = Tween(begin: 0.9, end: 1.0).animate(
  CurvedAnimation(parent: _ctrl, curve: const Interval(0.2, 0.8, curve: Curves.elasticOut)),
);
Enter fullscreen mode Exit fullscreen mode

Hero Transitions

// List screen
Hero(tag: 'task-${task.id}', child: TaskCard(task: task))

// Detail screen
Hero(tag: 'task-${task.id}', child: TaskDetailHeader(task: task))
// Works out of the box with GoRouter
Enter fullscreen mode Exit fullscreen mode

AnimatedSwitcher

AnimatedSwitcher(
  duration: const Duration(milliseconds: 300),
  transitionBuilder: (child, animation) => FadeTransition(
    opacity: animation,
    child: ScaleTransition(scale: animation, child: child),
  ),
  child: isLoading
      ? const CircularProgressIndicator(key: ValueKey('loading'))
      : TaskContent(key: ValueKey(task.id), task: task),
)
Enter fullscreen mode Exit fullscreen mode

Implicit Animations (Simplest)

// AnimatedContainer
AnimatedContainer(
  duration: const Duration(milliseconds: 300),
  curve: Curves.easeInOut,
  width: isExpanded ? 300 : 100,
  color: isExpanded ? Colors.blue : Colors.grey,
)

// AnimatedOpacity
AnimatedOpacity(opacity: isVisible ? 1.0 : 0.0, duration: const Duration(milliseconds: 200), child: w)
Enter fullscreen mode Exit fullscreen mode

Rive (Designer-authored)

Complex animations built in Rive editor, played in Flutter.

dependencies:
  rive: ^0.13.0
Enter fullscreen mode Exit fullscreen mode
RiveAnimation.network(
  'https://cdn.rive.app/animations/vehicles.riv',
  stateMachines: ['Drive'],
  onInit: (artboard) {
    final ctrl = StateMachineController.fromArtboard(artboard, 'Drive')!;
    artboard.addController(ctrl);
    (ctrl.findInput<double>('Speed')! as SMINumber).value = 50;
  },
)
Enter fullscreen mode Exit fullscreen mode

Performance Tips

  • Wrap animated widgets in RepaintBoundary
  • Prefer FadeTransition over Opacity widget (GPU layer)
  • Profile with flutter run --profile to verify 60fps
  • Keep const constructors everywhere you can

Adding micro-animations increased average session time by 23% in my app.


What's your go-to Flutter animation pattern? Drop a comment below.

Top comments (0)