Animazioni esplicite in Flutter con AnimationController e Tween
GuideIntermedio35 min Flutter 3.x

Animazioni esplicite in Flutter con AnimationController e Tween

Le animazioni implicite come AnimatedContainer sono comode, ma quando hai bisogno di un controllo preciso sulla durata, sulla ripetizione o sull'avvio di un'animazione, servono le animazioni esplicite.

In questo tutorial costruiremo un'animazione gestita manualmente tramite AnimationController, definiremo i valori con Tween e ricostruiremo solo la parte necessaria della UI usando AnimatedBuilder. Vedremo anche le curve di animazione e come avviare, fermare e ripetere l'animazione.

Al termine avrai una solida base per creare animazioni complesse e performanti.

  1. 1

    Preparare lo StatefulWidget e il TickerProvider

    Le animazioni esplicite richiedono un AnimationController, che a sua volta ha bisogno di un TickerProvider per sincronizzarsi con il refresh dello schermo.

    Creiamo uno StatefulWidget e aggiungiamo il mixin SingleTickerProviderStateMixin, che fornisce il ticker tramite vsync: this.

    import 'package:flutter/material.dart';
    
    class PulseDemo extends StatefulWidget {
      const PulseDemo({super.key});
    
      @override
      State<PulseDemo> createState() => _PulseDemoState();
    }
    
    class _PulseDemoState extends State<PulseDemo>
        with SingleTickerProviderStateMixin {
      @override
      Widget build(BuildContext context) {
        return const Scaffold();
      }
    }

    Risultato atteso

    Un widget stateful pronto a ospitare un AnimationController grazie al mixin SingleTickerProviderStateMixin.

  2. 2

    Creare e configurare l'AnimationController

    Inizializziamo l'AnimationController dentro initState, specificando la durata e il vsync. È fondamentale richiamare dispose() per liberare le risorse ed evitare memory leak.

    In questo esempio l'animazione durerà 1 secondo e si ripeterà avanti e indietro grazie a repeat(reverse: true).

    late final AnimationController _controller;
    
    @override
    void initState() {
      super.initState();
      _controller = AnimationController(
        vsync: this,
        duration: const Duration(seconds: 1),
      )..repeat(reverse: true);
    }
    
    @override
    void dispose() {
      _controller.dispose();
      super.dispose();
    }

    Risultato atteso

    Il controller produce valori da 0.0 a 1.0 in modo continuo, oscillando avanti e indietro.

  3. 3

    Definire l'animazione con Tween e una Curve

    L'AnimationController genera valori tra 0.0 e 1.0. Per mappare questo intervallo su valori utili (ad esempio una scala da 1.0 a 1.5) usiamo un Tween.

    Aggiungiamo anche una CurvedAnimation per rendere il movimento più naturale, usando ad esempio Curves.easeInOut.

    late final Animation<double> _scaleAnimation;
    
    @override
    void initState() {
      super.initState();
      _controller = AnimationController(
        vsync: this,
        duration: const Duration(seconds: 1),
      )..repeat(reverse: true);
    
      _scaleAnimation = Tween<double>(begin: 1.0, end: 1.5).animate(
        CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
      );
    }

    Risultato atteso

    _scaleAnimation fornisce valori tra 1.0 e 1.5 seguendo una curva fluida.

  4. 4

    Ricostruire la UI con AnimatedBuilder

    Per applicare l'animazione alla UI in modo performante usiamo AnimatedBuilder: ricostruisce solo il widget interessato a ogni frame, senza ricostruire l'intero albero.

    Il parametro child viene passato una sola volta e riutilizzato, ottimizzando ulteriormente le prestazioni.

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: const Text('Pulse Animation')),
        body: Center(
          child: AnimatedBuilder(
            animation: _scaleAnimation,
            builder: (context, child) {
              return Transform.scale(
                scale: _scaleAnimation.value,
                child: child,
              );
            },
            child: Container(
              width: 120,
              height: 120,
              decoration: const BoxDecoration(
                color: Colors.deepPurple,
                shape: BoxShape.circle,
              ),
              child: const Icon(Icons.favorite, color: Colors.white, size: 48),
            ),
          ),
        ),
      );
    }

    Risultato atteso

    Un cerchio viola con un'icona che pulsa continuamente ingrandendosi e rimpicciolendosi.

  5. 5

    Controllare l'animazione con i pulsanti

    Spesso vorrai avviare o fermare l'animazione in risposta a un'azione dell'utente. L'AnimationController offre i metodi forward(), reverse(), stop() e reset().

    Modifichiamo l'esempio rimuovendo il repeat automatico e aggiungendo due pulsanti per controllare l'animazione manualmente.

    // In initState rimuovi `..repeat(reverse: true)`
    
    floatingActionButton: Row(
      mainAxisAlignment: MainAxisAlignment.end,
      children: [
        FloatingActionButton(
          heroTag: 'play',
          onPressed: () => _controller.forward(),
          child: const Icon(Icons.play_arrow),
        ),
        const SizedBox(width: 12),
        FloatingActionButton(
          heroTag: 'reset',
          onPressed: () => _controller.reverse(),
          child: const Icon(Icons.replay),
        ),
      ],
    ),

    Risultato atteso

    Premendo play l'icona si ingrandisce una volta; premendo reset torna alla dimensione iniziale.

  6. 6

    Reagire allo stato dell'animazione con un listener

    Puoi monitorare lo stato dell'animazione (avanzamento completato, inversione, ecc.) con addStatusListener. Questo è utile, ad esempio, per concatenare più animazioni o creare effetti di ping-pong personalizzati.

    Ricorda di aggiungere il listener in initState.

    @override
    void initState() {
      super.initState();
      _controller = AnimationController(
        vsync: this,
        duration: const Duration(seconds: 1),
      );
    
      _scaleAnimation = Tween<double>(begin: 1.0, end: 1.5).animate(
        CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
      );
    
      _controller.addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          _controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          _controller.forward();
        }
      });
    
      _controller.forward();
    }

    Risultato atteso

    L'animazione crea un effetto ping-pong continuo controllato manualmente tramite lo status listener.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!