[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"articolo-animazioni-dichiarative-in-flutter-con-flutter-animate-effetti-complessi-in-poche-righe":3,"comments-article-animazioni-dichiarative-in-flutter-con-flutter-animate-effetti-complessi-in-poche-righe":26},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"status":16,"published_at":17,"meta_title":18,"meta_description":19,"category":20,"author":24},125,"Animazioni dichiarative in Flutter con flutter_animate: effetti complessi in poche righe","animazioni-dichiarative-in-flutter-con-flutter-animate-effetti-complessi-in-poche-righe","flutter_animate permette di comporre animazioni complesse concatenando effetti su qualsiasi widget, senza AnimationController né boilerplate. Guida pratica a effetti, sequenze, stagger, animazioni guidate dallo stato, effetti custom e accessibilità.","## Perché un pacchetto per le animazioni\n\nFlutter offre un sistema di animazione completo: widget impliciti (`AnimatedContainer`, `AnimatedOpacity`) per le transizioni semplici e `AnimationController` + `Tween` per il controllo totale. Il problema nasce quando servono **sequenze di più effetti combinati**: un elemento che compare in dissolvenza, scivola dal basso, pulsa leggermente e poi resta fermo. Con l'approccio esplicito significa uno `StatefulWidget`, un `TickerProvider`, più `Animation` con `Interval` calcolati a mano e decine di righe di codice per ogni elemento.\n\n[`flutter_animate`](https:\u002F\u002Fpub.dev\u002Fpackages\u002Fflutter_animate) risolve esattamente questo: espone un'API dichiarativa e concatenabile che si applica a qualsiasi widget tramite l'extension method `.animate()`. È sviluppato da Grant Skinner (gskinner), è maturo, non ha dipendenze native e non sostituisce il sistema di animazione di Flutter: lo incapsula.\n\n## Installazione\n\n```yaml\ndependencies:\n  flutter_animate: ^4.5.2\n```\n\n```dart\nimport 'package:flutter_animate\u002Fflutter_animate.dart';\n```\n\nIl pacchetto esporta anche le estensioni numeriche per le durate, quindi `500.ms`, `2.seconds`, `1.5.minutes` sono subito disponibili.\n\n## Il primo effetto\n\n```dart\nText('Ciao Flutter')\n    .animate()\n    .fadeIn(duration: 600.ms)\n    .slideY(begin: 0.3, end: 0);\n```\n\nTre cose da notare:\n\n- `.animate()` avvolge il widget in un `Animate`, che internamente crea e gestisce un `AnimationController`.\n- Gli effetti concatenati sono **paralleli di default**: partono tutti insieme al tempo zero.\n- I valori di `slideY` sono frazioni dell'altezza del widget: `0.3` significa \"parti 30% più in basso\".\n\nSe non si specifica nulla, l'animazione parte automaticamente al primo build (`autoPlay: true`).\n\n## Durata, delay e curve\n\nOgni effetto accetta `delay`, `duration` e `curve`. Se omessi, eredita i valori dell'effetto precedente o i default globali.\n\n```dart\nCard(child: ListTile(title: Text('Notifica')))\n    .animate()\n    .fadeIn(duration: 400.ms, curve: Curves.easeOut)\n    .scaleXY(begin: 0.95, end: 1, curve: Curves.easeOutBack)\n    .shimmer(delay: 400.ms, duration: 900.ms, color: Colors.white54);\n```\n\n## Sequenze con then()\n\n`.then()` sposta il punto di partenza dell'effetto successivo alla fine del precedente, con un eventuale delay aggiuntivo. È il modo più leggibile per costruire timeline.\n\n```dart\nIcon(Icons.check_circle, size: 72, color: Colors.green)\n    .animate()\n    .scaleXY(begin: 0, end: 1, duration: 350.ms, curve: Curves.easeOutBack)\n    .then(delay: 150.ms)\n    .shake(hz: 3, rotation: 0.02)\n    .then()\n    .fadeOut(duration: 400.ms);\n```\n\nSenza `then()` i tre effetti si sovrapporrebbero; con `then()` si ottiene: comparsa → piccola scossa → uscita.\n\n## Stagger: animare una lista di widget\n\nL'extension `.animate()` esiste anche su `List\u003CWidget>` e restituisce un `AnimateList`, che applica gli stessi effetti a tutti i figli sfalsandoli con `interval`.\n\n```dart\nColumn(\n  crossAxisAlignment: CrossAxisAlignment.start,\n  children: [\n    Text('Titolo', style: Theme.of(context).textTheme.headlineSmall),\n    const SizedBox(height: 8),\n    const Text('Sottotitolo descrittivo'),\n    const SizedBox(height: 16),\n    FilledButton(onPressed: () {}, child: const Text('Inizia')),\n  ].animate(interval: 90.ms).fadeIn(duration: 350.ms).slideX(begin: -0.1),\n)\n```\n\nFunziona bene per schermate di onboarding, form e dashboard. Attenzione: in una `ListView.builder` con migliaia di elementi, conviene applicare `.animate()` al singolo item costruito, non all'intera lista.\n\n## Animazioni guidate dallo stato con target\n\nFinora abbiamo visto animazioni \"una tantum\". Per animazioni che reagiscono allo stato si usa il parametro `target`: cambiandone il valore, `Animate` interpola verso quella posizione della timeline (0 = inizio, 1 = fine) invece di saltare.\n\n```dart\nclass LikeButton extends StatefulWidget {\n  const LikeButton({super.key});\n\n  @override\n  State\u003CLikeButton> createState() => _LikeButtonState();\n}\n\nclass _LikeButtonState extends State\u003CLikeButton> {\n  bool _liked = false;\n\n  @override\n  Widget build(BuildContext context) {\n    return IconButton(\n      onPressed: () => setState(() => _liked = !_liked),\n      icon: const Icon(Icons.favorite)\n          .animate(target: _liked ? 1 : 0)\n          .tint(color: Colors.red, duration: 200.ms)\n          .scaleXY(end: 1.25, duration: 200.ms, curve: Curves.easeOut)\n          .then()\n          .scaleXY(end: 1 \u002F 1.25, duration: 150.ms),\n    );\n  }\n}\n```\n\nQuando `_liked` torna `false`, l'animazione viene riprodotta al contrario in modo fluido. È il pattern da usare per toggle, stati di selezione, badge attivi\u002Finattivi.\n\n## Loop, shimmer e skeleton\n\nPer le animazioni continue si interviene sul controller tramite la callback `onPlay`:\n\n```dart\nContainer(\n  height: 18,\n  decoration: BoxDecoration(\n    color: Colors.grey.shade300,\n    borderRadius: BorderRadius.circular(4),\n  ),\n)\n    .animate(onPlay: (controller) => controller.repeat())\n    .shimmer(duration: 1200.ms, color: Colors.white);\n```\n\nAltri casi tipici: `controller.repeat(reverse: true)` per un respiro pulsante, oppure `onComplete` per concatenare logica applicativa al termine dell'animazione.\n\n```dart\nconst Icon(Icons.notifications)\n    .animate(onPlay: (c) => c.repeat(reverse: true))\n    .scaleXY(end: 1.08, duration: 800.ms, curve: Curves.easeInOut);\n```\n\n## Controllo manuale con un AnimationController esterno\n\nQuando l'animazione deve essere innescata da un evento (un errore di validazione, l'arrivo di un messaggio) si passa un controller proprio e si disattiva l'autoplay. `Animate` adatta la durata del controller alla durata complessiva degli effetti.\n\n```dart\nclass ShakeField extends StatefulWidget {\n  const ShakeField({super.key});\n\n  @override\n  State\u003CShakeField> createState() => _ShakeFieldState();\n}\n\nclass _ShakeFieldState extends State\u003CShakeField>\n    with SingleTickerProviderStateMixin {\n  late final AnimationController _controller =\n      AnimationController(vsync: this);\n\n  @override\n  void dispose() {\n    _controller.dispose();\n    super.dispose();\n  }\n\n  void _onError() => _controller.forward(from: 0);\n\n  @override\n  Widget build(BuildContext context) {\n    return Column(\n      children: [\n        const TextField()\n            .animate(controller: _controller, autoPlay: false)\n            .shakeX(hz: 5, amount: 6, duration: 400.ms),\n        TextButton(onPressed: _onError, child: const Text('Simula errore')),\n      ],\n    );\n  }\n}\n```\n\n## Effetti custom\n\nQuando gli effetti inclusi non bastano, `.custom()` espone direttamente il valore interpolato:\n\n```dart\nText('Caricamento')\n    .animate(onPlay: (c) => c.repeat())\n    .custom(\n      duration: 1500.ms,\n      builder: (context, value, child) => Opacity(\n        opacity: 0.4 + (0.6 * (1 - (value - 0.5).abs() * 2)),\n        child: child,\n      ),\n    );\n```\n\nPer riutilizzare un effetto composto in tutta l'app conviene scrivere un'extension su `AnimateManager`, così funziona sia su `Animate` sia su `AnimateList`:\n\n```dart\nextension AppEffects\u003CT> on AnimateManager\u003CT> {\n  \u002F\u002F\u002F Comparsa standard dell'app: fade + leggero ingrandimento.\n  T appear({Duration? delay}) => fadeIn(\n        delay: delay,\n        duration: 300.ms,\n        curve: Curves.easeOut,\n      ).scaleXY(begin: 0.96, end: 1, curve: Curves.easeOutCubic);\n}\n\n\u002F\u002F Uso\nMyCard().animate().appear();\n[cardA, cardB, cardC].animate(interval: 70.ms).appear();\n```\n\nIn questo modo il *motion design* dell'applicazione resta centralizzato e coerente, esattamente come si fa con il `ThemeData`.\n\n## Configurazione globale e hot reload\n\nNel `main()` è utile impostare i default e abilitare il restart delle animazioni a ogni hot reload durante lo sviluppo:\n\n```dart\nvoid main() {\n  Animate.defaultDuration = 350.ms;\n  Animate.defaultCurve = Curves.easeOut;\n  assert(() {\n    Animate.restartOnHotReload = true;\n    return true;\n  }());\n  runApp(const MyApp());\n}\n```\n\nL'`assert` garantisce che `restartOnHotReload` resti attivo solo in debug: in release il blocco viene rimosso dal compilatore.\n\n## Accessibilità: rispettare il \"reduce motion\"\n\nSu iOS e Android l'utente può chiedere di ridurre le animazioni. Ignorare questa impostazione è un problema di accessibilità concreto (vertigini, disturbi vestibolari). Flutter la espone tramite `MediaQuery`:\n\n```dart\nclass Appear extends StatelessWidget {\n  const Appear({super.key, required this.child});\n\n  final Widget child;\n\n  @override\n  Widget build(BuildContext context) {\n    final reduceMotion = MediaQuery.disableAnimationsOf(context);\n    if (reduceMotion) return child;\n    return child.animate().fadeIn(duration: 300.ms).slideY(begin: 0.15);\n  }\n}\n```\n\nUna variante più morbida consiste nel mantenere solo i fade (percettivamente innocui) ed eliminare movimenti e scale.\n\n## Performance: cosa tenere d'occhio\n\n- **Ogni `.animate()` crea un `AnimationController`.** Decine di controller contemporanei in una lista scrollabile costano; animare solo gli elementi effettivamente visibili e preferire animazioni brevi e non ripetute.\n- **Le animazioni in loop non si fermano da sole** quando il widget esce dallo schermo in un `CustomScrollView` con `addAutomaticKeepAlives`. Evitare `repeat()` su elementi fuori viewport.\n- **Sfruttare il parametro `child`.** Gli effetti usano internamente un pattern `AnimatedBuilder`: il sottoalbero passato come `child` non viene ricostruito a ogni frame, solo ridisegnato. Usare `const` dove possibile amplifica il vantaggio.\n- **Attenzione a blur e shimmer.** `blur()` e gli effetti con `ShaderMask` sono i più costosi: su device di fascia bassa vanno misurati con DevTools (timeline raster).\n- **Animare le foglie, non i rami.** Applicare `.animate()` il più vicino possibile al widget che cambia riduce l'area di repaint; in alcuni casi conviene aggiungere un `RepaintBoundary`.\n\n## Quando non usarlo\n\n`flutter_animate` è ideale per micro-interazioni, comparse, transizioni di stato e feedback visivi. Non è la scelta giusta quando serve:\n\n- un'animazione **gesture-driven** con controllo frame per frame (drag interattivo): meglio `AnimationController` + `GestureDetector`;\n- una **transizione di route** complessa: resta l'ambito di `PageRouteBuilder` e `Hero`;\n- grafica vettoriale animata da designer: lì ha più senso Rive o Lottie.\n\n## Conclusione\n\n`flutter_animate` non introduce un nuovo motore di animazione: aggiunge un livello dichiarativo sopra quello esistente, eliminando il boilerplate che scoraggia dal curare i dettagli di movimento. Il risultato pratico è che diventa economico aggiungere micro-interazioni coerenti in tutta l'app, soprattutto se si centralizzano gli effetti in extension condivise. Come sempre, la regola d'oro resta la stessa: animazioni brevi (200–400 ms), con una funzione comunicativa chiara e disattivabili quando l'utente lo chiede.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002Fa0d8472e-1423-4289-a435-771c31564642.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1735754947091-13f2c280a4e6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3OTE3Nzc3MTd8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Creatvise","https:\u002F\u002Funsplash.com\u002F@creatvise","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fa-kite-flying-through-the-air-with-a-purple-kite-BDGalDwnBe0",null,"published","2026-10-12T04:01:57+00:00","flutter_animate: animazioni dichiarative in Flutter","Guida a flutter_animate: effetti concatenati, sequenze con then, stagger, animazioni guidate dallo stato, effetti custom, performance e accessibilità.",{"id":21,"name":22,"slug":23},1,"Guide","guide",{"id":21,"name":25},"Flutter Bot",[],1792316902139]