Introduzione

Le animazioni sono uno degli aspetti che distinguono un'app mediocre da una eccellente. Flutter offre un sistema di animazioni potente e flessibile, capace di garantire 60 (o anche 120) frame al secondo. In questo articolo vedremo le due grandi famiglie di animazioni: implicite ed esplicite.

Animazioni implicite

Le animazioni implicite sono il modo più semplice per animare un widget. Basta cambiare un valore e Flutter si occupa di interpolare automaticamente la transizione. Tutti questi widget iniziano con il prefisso Animated.

class BoxDemo extends StatefulWidget {
  const BoxDemo({super.key});

  @override
  State<BoxDemo> createState() => _BoxDemoState();
}

class _BoxDemoState extends State<BoxDemo> {
  bool _expanded = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _expanded = !_expanded),
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 400),
        curve: Curves.easeInOut,
        width: _expanded ? 200 : 100,
        height: _expanded ? 200 : 100,
        color: _expanded ? Colors.deepPurple : Colors.teal,
      ),
    );
  }
}

Ogni volta che cambia lo stato, AnimatedContainer anima la transizione tra i vecchi e i nuovi valori. Tra gli altri widget impliciti più utili troviamo:

  • AnimatedOpacity per dissolvenze
  • AnimatedPadding e AnimatedAlign per spostamenti
  • AnimatedDefaultTextStyle per il testo
  • TweenAnimationBuilder per animare qualsiasi valore

TweenAnimationBuilder

Quando serve animare una proprietà non coperta da un widget specifico, TweenAnimationBuilder è la scelta ideale:

TweenAnimationBuilder<double>(
  tween: Tween(begin: 0, end: 1),
  duration: const Duration(seconds: 1),
  builder: (context, value, child) {
    return Transform.rotate(
      angle: value * 2 * 3.1415,
      child: child,
    );
  },
  child: const Icon(Icons.refresh, size: 64),
)

Animazioni esplicite

Quando hai bisogno di un controllo totale (ripetizione, inversione, sincronizzazione tra più animazioni) entrano in gioco le animazioni esplicite, basate su AnimationController.

L'AnimationController richiede un TickerProvider, che si ottiene tipicamente con il mixin SingleTickerProviderStateMixin.

class PulseDemo extends StatefulWidget {
  const PulseDemo({super.key});

  @override
  State<PulseDemo> createState() => _PulseDemoState();
}

class _PulseDemoState extends State<PulseDemo>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _scale;

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

    _scale = Tween<double>(begin: 0.8, end: 1.2).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );
  }

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

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

Punti chiave

  • Ricorda sempre dispose() del controller per evitare memory leak.
  • I Tween definiscono l'intervallo di valori, le Curve la velocità nel tempo.
  • I widget *Transition (FadeTransition, SlideTransition, RotationTransition) consumano direttamente un'Animation.

AnimatedBuilder

Per logiche personalizzate, AnimatedBuilder evita di ricostruire l'intero albero dei widget:

AnimatedBuilder(
  animation: _controller,
  builder: (context, child) {
    return Opacity(
      opacity: _controller.value,
      child: child,
    );
  },
  child: const Text('Ciao Flutter'),
)

Il parametro child viene costruito una sola volta e passato al builder, ottimizzando le prestazioni.

Quando usare cosa

  • Animazioni implicite: transizioni semplici di stato, codice minimale.
  • Animazioni esplicite: cicli, sincronizzazioni, controllo manuale e interazioni complesse.

Conclusione

Flutter rende le animazioni accessibili senza sacrificare la potenza. Inizia con i widget impliciti per i casi comuni e passa agli AnimationController quando hai bisogno di un controllo fine. Per esigenze ancora più avanzate, dai un'occhiata a librerie come Rive, Lottie o al pacchetto flutter_animate, che permette di concatenare animazioni con poche righe di codice.