.claude/skills/flutter-animating-apps/SKILL.md
Implements animated effects, transitions, and motion in a Flutter app. Covers implicit animations (AnimatedContainer, AnimatedOpacity, TweenAnimationBuilder), explicit animations (AnimationController, Tween, CurvedAnimation, AnimatedBuilder), Hero transitions, staggered animations, physics-based animations (SpringSimulation), page route transitions, and AnimatedList. Use when adding visual feedback, shared element transitions, physics-based animations, loading skeletons, shimmer effects, or animated onboarding flows. Activate even when the user says 'animate this widget', 'smooth transition between screens', 'fade in on load', 'slide from bottom', 'bouncy button effect', 'Hero animation between pages', 'make this feel more fluid', or 'animate a list item appearing' without explicitly mentioning AnimationController or Tween.
npx skillsauth add andrelucassvt/CleanMacForDevsWeb flutter-animating-appsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Manage Flutter animations using the core typed Animation system. Do not manually calculate frames; rely on the framework's ticker and interpolation classes.
Animation<T>: Treat this as an abstract representation of a value that changes over time. It holds state (completed, dismissed) and notifies listeners, but knows nothing about the UI.AnimationController: Instantiate this to drive the animation. It generates values (typically 0.0 to 1.0) tied to the screen refresh rate. Always provide a vsync (usually via SingleTickerProviderStateMixin) to prevent offscreen resource consumption. Always dispose() controllers to prevent memory leaks.Tween<T>: Define a stateless mapping from an input range (usually 0.0-1.0) to an output type (e.g., Color, Offset, double). Chain tweens with curves using .animate().Curve: Apply non-linear timing (e.g., Curves.easeIn, Curves.bounceOut) to an animation using a CurvedAnimation or CurveTween.Apply conditional logic to select the correct animation approach:
AnimatedContainer, AnimatedOpacity, TweenAnimationBuilder).AnimationController with AnimatedBuilder or AnimatedWidget).SpringSimulation).Tweens driven by a single AnimationController using Interval curves).Use this workflow for "fire-and-forget" state-driven animations.
Container) with its animated counterpart (e.g., AnimatedContainer).duration property.curve property for non-linear motion.setState() call.Use this workflow when you need granular control over the animation lifecycle.
SingleTickerProviderStateMixin (or TickerProviderStateMixin for multiple controllers) to the State class.AnimationController in initState(), providing vsync: this and a duration.Tween and chain it to the controller using .animate().AnimatedBuilder (preferred for complex trees) or subclass AnimatedWidget.Animation object to the AnimatedBuilder's animation property.controller.forward(), controller.reverse(), or controller.repeat().controller.dispose() in the dispose() method.dispose() is called.Use this workflow to fly a widget between two routes.
Hero widget.tag to the source Hero.Hero widget.tag to the destination Hero.Hero widgets are visually similar to prevent jarring jumps.Navigator.Use this workflow for gesture-driven, natural motion.
AnimationController (do not set a fixed duration).GestureDetector (e.g., onPanEnd providing DragEndDetails).SpringSimulation with mass, stiffness, damping, and the calculated velocity.controller.animateWith(simulation).class StaggeredAnimationDemo extends StatefulWidget {
@override
State<StaggeredAnimationDemo> createState() => _StaggeredAnimationDemoState();
}
class _StaggeredAnimationDemoState extends State<StaggeredAnimationDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _widthAnimation;
late Animation<Color?> _colorAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
// Staggered width animation (0.0 to 0.5 interval)
_widthAnimation = Tween<double>(begin: 50.0, end: 200.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.5, curve: Curves.easeIn),
),
);
// Staggered color animation (0.5 to 1.0 interval)
_colorAnimation = ColorTween(begin: Colors.blue, end: Colors.red).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.5, 1.0, curve: Curves.easeOut),
),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose(); // CRITICAL: Prevent memory leaks
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Container(
width: _widthAnimation.value,
height: 50.0,
color: _colorAnimation.value,
);
},
);
}
}
</details>
<details>
<summary><b>Example: Custom Page Route Transition</b></summary>
Route createCustomRoute(Widget destination) {
return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => destination,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(0.0, 1.0); // Start from bottom
const end = Offset.zero;
const curve = Curves.easeOut;
final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
final offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
},
);
}
// Usage: Navigator.of(context).push(createCustomRoute(const NextPage()));
</details>
Evite estes erros comuns ao implementar animações:
| Anti-pattern | Por quê é ruim | Correto |
|---|---|---|
| Esquecer controller.dispose() no dispose() | Vazamento de memória — ticker continua rodando após widget ser desmontado | Sempre chame controller.dispose() em dispose() |
| Criar AnimationController sem vsync | Animação continua consumindo recursos mesmo quando a tela não está visível | Use SingleTickerProviderStateMixin e passe vsync: this |
| TickerProviderStateMixin com um único controller | Funciona, mas indica uso incorreto do mixin | Use SingleTickerProviderStateMixin para 1 controller; TickerProviderStateMixin para 2+ |
| Usar setState() com addListener() para rebuildar UI | Reconstrói toda a subárvore — causa jank em árvores complexas | Use AnimatedBuilder que reconstrói apenas o builder |
| Animar dentro de build() (controller.forward() no build) | Cria loop infinito de builds ou reinicia a animação a cada rebuild | Inicie animações em initState(), callbacks ou BlocListener |
| Duration.zero ou duração extremamente curta | Animação imperceptível, transição brusca — igual a não animar | Use pelo menos Duration(milliseconds: 150) para feedback visual |
| Animação implícita para sequências complexas | Sem controle de playback, sem stagger, sem reverse sincronizado | Use AnimationController + Interval para coreografar sequências |
| Hero com tags duplicadas na mesma rota | Crash ou comportamento inesperado na transição | Garanta tags únicos por rota (use ID do dado, não string fixa) |
| AnimationController com duration fixo para physics | Ignora velocidade real do gesto, movimento artificial | Omita duration e use controller.animateWith(simulation) |
| Múltiplos RepaintBoundary desnecessários | Custo de memória para cada layer extra sem ganho real de performance | Use RepaintBoundary apenas em animações que causam repaint do pai |
Última atualização: 11 de abril de 2026
testing
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy. Activate even when the user says 'create a skill for X', 'the skill is not triggering', 'improve this skill description', 'the agent is not using the skill', 'add a skill to teach the agent how to do X', 'this skill is wrong', or 'update the skill' without explicitly mentioning evals or benchmark.
development
Implements Flutter reusable widgets following the project architecture. Use whenever creating or modifying widgets in presentation/<feature>/widgets/, presentation/<feature>/content/, or common/widgets/. Covers StatelessWidget vs StatefulWidget decision, Entity as parameter, i18n, dispose, componentization rules, and when to access the Cubit via context.read. Activate even when the user says 'extract this to a widget', 'create a list item widget', 'build a reusable card', 'factor out this UI block', 'create a component for this', or 'this View is getting too big' without explicitly mentioning StatelessWidget or reusable components.
tools
Implements Flutter View screens following the project architecture. Use whenever creating or modifying a View (StatefulWidget + Cubit + BlocBuilder), adding a new screen, wiring up BlocBuilder/BlocConsumer/BlocListener, setting up SafeArea, or navigating from the View. Covers State, Cubit, View file, route, DI registration, and common mistakes. Activate even when the user just says "create a screen" or "add a new page", without explicitly mentioning Cubit or BLoC.
testing
Implements Flutter Cubit and State (View Model layer) following the project architecture. Use whenever creating or modifying a Cubit or State class, adding an async method to a Cubit, handling form submission or validation, implementing debounce search, managing loading/error/navigation states, or wiring a Cubit to a Repository or StorageService. Covers sealed States, async patterns with Result<T>, CRUD Cubits, local persistence via StorageService, navigation states, debounce, and common mistakes. Activate even when the user says "add a method", "handle the loading state", or "save locally" without explicitly mentioning Cubit or BLoC.