# Flutter Animations

> When to activate: Flutter animations, AnimationController, Tween, Hero, PageRoute, implicit animations, explicit animations, Lottie, Rive

- Skill: `mattakushi432/flutter-animations` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/flutter-animations`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/flutter-animations/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/flutter-animations

---

# Flutter Animation Patterns

## Implicit Animations (prefer for simple transitions)

```dart
// AnimatedContainer, AnimatedOpacity, AnimatedPositioned, etc.
AnimatedContainer(
  duration: const Duration(milliseconds: 300),
  curve: Curves.easeInOut,
  width: _expanded ? 200 : 100,
  height: _expanded ? 200 : 100,
  color: _expanded ? Colors.blue : Colors.red,
  child: const FlutterLogo(),
);

// AnimatedSwitcher: animate between two different widgets
AnimatedSwitcher(
  duration: const Duration(milliseconds: 300),
  transitionBuilder: (child, animation) =>
    FadeTransition(opacity: animation, child: child),
  child: Text('$_count', key: ValueKey(_count)),
);

// TweenAnimationBuilder: custom implicit animation
TweenAnimationBuilder<double>(
  tween: Tween(begin: 0, end: _progress),
  duration: const Duration(milliseconds: 500),
  curve: Curves.easeOut,
  builder: (context, value, child) =>
    LinearProgressIndicator(value: value),
);
```

## Explicit Animations with AnimationController

```dart
class PulseWidget extends StatefulWidget {
  const PulseWidget({super.key});
  @override
  State<PulseWidget> createState() => _PulseWidgetState();
}

class _PulseWidgetState extends State<PulseWidget>
    with SingleTickerProviderStateMixin {
  late final AnimationController _ctrl;
  late final Animation<double> _scale;

  @override
  void initState() {
    super.initState();
    _ctrl = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 800),
    )..repeat(reverse: true);

    _scale = Tween(begin: 1.0, end: 1.2).animate(
      CurvedAnimation(parent: _ctrl, curve: Curves.easeInOut),
    );
  }

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

  @override
  Widget build(BuildContext context) => ScaleTransition(
    scale: _scale,
    child: const FlutterLogo(size: 100),
  );
}
```

## Staggered Animations

```dart
class _StaggeredState extends State<StaggeredList>
    with SingleTickerProviderStateMixin {
  late final AnimationController _ctrl;

  late final List<Animation<Offset>> _slides = List.generate(5, (i) {
    final start = i * 0.15;
    return Tween(begin: const Offset(1, 0), end: Offset.zero).animate(
      CurvedAnimation(
        parent: _ctrl,
        curve: Interval(start, (start + 0.4).clamp(0, 1), curve: Curves.easeOut),
      ),
    );
  });

  @override
  void initState() {
    super.initState();
    _ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 1200));
    _ctrl.forward();
  }

  @override
  Widget build(BuildContext context) => Column(
    children: List.generate(5, (i) => SlideTransition(
      position: _slides[i],
      child: ListTile(title: Text('Item $i')),
    )),
  );
}
```

## Hero Animations

```dart
// Source page
GestureDetector(
  onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => DetailPage())),
  child: Hero(
    tag: 'product-${product.id}', // must be unique across tree
    child: Image.network(product.imageUrl, width: 80),
  ),
);

// Destination page
Hero(
  tag: 'product-${product.id}',
  child: Image.network(product.imageUrl, width: double.infinity),
);
```

## Custom Page Transitions

```dart
class SlideUpRoute extends PageRouteBuilder {
  SlideUpRoute({required this.page})
    : super(
        pageBuilder: (_, __, ___) => page,
        transitionsBuilder: (_, animation, __, child) => SlideTransition(
          position: Tween(
            begin: const Offset(0, 1),
            end: Offset.zero,
          ).animate(CurvedAnimation(parent: animation, curve: Curves.easeOut)),
          child: child,
        ),
      );
  final Widget page;
}

// GoRouter custom transition
GoRoute(
  path: '/modal',
  pageBuilder: (context, state) => CustomTransitionPage(
    key: state.pageKey,
    child: const ModalPage(),
    transitionsBuilder: (_, animation, __, child) =>
      FadeTransition(opacity: animation, child: child),
  ),
),
```

## Lottie Animations

```dart
// pubspec: lottie: ^3.0.0
import 'package:lottie/lottie.dart';

// From assets
Lottie.asset('assets/animations/loading.json', width: 200, repeat: true);

// Controlled
LottieBuilder.asset(
  'assets/animations/success.json',
  controller: _ctrl,
  onLoaded: (comp) {
    _ctrl.duration = comp.duration;
    _ctrl.forward();
  },
);
```

## Rive Animations

```dart
// pubspec: rive: ^0.13.0
RiveAnimation.asset(
  'assets/animations/character.riv',
  animations: const ['idle'],
  onInit: (artboard) {
    final controller = StateMachineController.fromArtboard(artboard, 'Main');
    artboard.addController(controller!);
    _runInput = controller.findInput<bool>('isRunning') as SMIBool;
  },
);
// Trigger state machine input
_runInput?.change(true);
```

## Reduced Motion

```dart
// Respect accessibility settings
class AccessibleAnimation extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final reduceMotion = MediaQuery.of(context).disableAnimations;
    return AnimatedContainer(
      duration: reduceMotion ? Duration.zero : const Duration(milliseconds: 300),
      child: const Placeholder(),
    );
  }
}
```

