Animazione di app con Flutter

Pubblicato: 2022-03-10
Riepilogo rapido ↬ Flutter fornisce un ottimo supporto per le animazioni per le app multipiattaforma. Questo articolo esplora il nuovo modo non convenzionale e più semplice per aggiungere animazioni alle app. Cosa sono esattamente questi nuovi widget "Animazione e movimento" e come possiamo usarli per aggiungere animazioni semplici e complesse?

Le app per qualsiasi piattaforma sono apprezzate quando sono intuitive, di bell'aspetto e forniscono un feedback piacevole alle interazioni degli utenti. L'animazione è uno dei modi per farlo.

Flutter, un framework multipiattaforma, è maturato negli ultimi due anni per includere il supporto Web e desktop. Si è guadagnato la reputazione che le app sviluppate con esso sono fluide e di bell'aspetto. Con il suo ricco supporto per le animazioni, il modo dichiarativo di scrivere l'interfaccia utente, "Hot Reload" e altre funzionalità, ora è un framework multipiattaforma completo.

Se stai iniziando con Flutter e vuoi imparare un modo non convenzionale per aggiungere animazioni, allora sei nel posto giusto: esploreremo il regno delle animazioni e dei widget di movimento, un modo implicito di aggiungere animazioni.

Flutter si basa sul concetto di widget. Ogni componente visivo di un'app è un widget: considerali come visualizzazioni in Android. Flutter fornisce supporto per l'animazione utilizzando una classe Animation, un oggetto "AnimationController" per la gestione e "Tween" per interpolare l'intervallo di dati. Questi tre componenti lavorano insieme per fornire un'animazione fluida. Poiché ciò richiede la creazione e la gestione manuale dell'animazione, è noto come un modo esplicito di animare.

Ora lascia che ti presenti l'animazione e i widget di movimento. Flutter fornisce numerosi widget che supportano intrinsecamente l'animazione. Non è necessario creare un oggetto di animazione o un controller, poiché tutta l'animazione è gestita da questa categoria di widget. Basta scegliere il widget appropriato per l'animazione richiesta e passare i valori delle proprietà del widget da animare. Questa tecnica è un modo implicito di animare.

Gerarchia dell'animazione in Flutter. (Grande anteprima)

Il grafico sopra definisce approssimativamente la gerarchia dell'animazione in Flutter, come sono supportate sia l'animazione esplicita che quella implicita.

Alcuni dei widget animati trattati in questo articolo sono:

  • AnimatedOpacity
  • AnimatedCrossFade
  • AnimatedAlign
  • AnimatedPadding
  • AnimatedSize
  • AnimatedPositioned .

Flutter non fornisce solo widget animati predefiniti, ma anche un widget generico chiamato AnimatedWidget , che può essere utilizzato per creare widget personalizzati animati in modo implicito. Come evidente dal nome, questi widget appartengono alla categoria dei widget animati e di movimento, quindi hanno alcune proprietà comuni che ci consentono di rendere le animazioni molto più fluide e di aspetto migliore.

Lascia che ti spieghi ora queste proprietà comuni, poiché verranno utilizzate più avanti in tutti gli esempi.

  • duration
    La durata durante la quale animare i parametri.
  • reverseDuration
    La durata dell'animazione inversa.
  • curve
    La curva da applicare durante l'animazione dei parametri. I valori interpolati possono essere presi da una distribuzione lineare o, se e quando specificato, possono essere presi da una curva.

Iniziamo il viaggio creando una semplice app che chiameremo “Quoted”. Verrà visualizzata una citazione casuale ogni volta che l'app si avvia. Due cose da notare: in primo luogo, tutte queste citazioni saranno codificate nell'applicazione; e in secondo luogo, nessun dato utente verrà salvato.

Nota : tutti i file per questi esempi possono essere trovati su GitHub.

Altro dopo il salto! Continua a leggere sotto ↓

Iniziare

Flutter dovrebbe essere installato e avrai bisogno di una certa familiarità con il flusso di base prima di andare avanti. Un buon punto di partenza è "Utilizzare Flutter di Google per uno sviluppo mobile davvero multipiattaforma".

Crea un nuovo progetto Flutter in Android Studio.

Nuovo menu del progetto Flutter in Android Studio. (Grande anteprima)

Si aprirà una nuova procedura guidata di progetto, in cui è possibile configurare le basi del progetto.

Schermata di selezione del tipo di progetto Flutter. (Grande anteprima)

Nella schermata di selezione del tipo di progetto, ci sono vari tipi di progetti Flutter, ciascuno adatto a uno scenario specifico. Per questo tutorial, scegli Applicazione Flutter e premi Avanti .

Ora devi inserire alcune informazioni specifiche del progetto: il nome e il percorso del progetto, il dominio dell'azienda e così via. Dai un'occhiata all'immagine qui sotto.

Schermata di configurazione dell'applicazione Flutter. (Grande anteprima)

Aggiungi il nome del progetto, il percorso dell'SDK Flutter, la posizione del progetto e una descrizione del progetto facoltativa. Premi Avanti .

Schermata del nome del pacchetto dell'applicazione Flutter. (Grande anteprima)

Ogni applicazione (sia essa Android o iOS) richiede un nome di pacchetto univoco. In genere, utilizzi il contrario del dominio del tuo sito web; ad esempio, com.google o com.yahoo. Premere Fine per generare un'applicazione Flutter funzionante.

Il progetto di esempio generato. (Grande anteprima)

Una volta generato il progetto, dovresti vedere la schermata mostrata sopra. Apri il file main.dart (evidenziato nello screenshot). Questo è il file principale dell'applicazione. Il progetto di esempio è completo in sé e può essere eseguito direttamente su un emulatore o su un dispositivo fisico senza alcuna modifica.

Sostituisci il contenuto del file main.dart con il seguente frammento di codice:

 import 'package:animated_widgets/FirstPage.dart'; import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Animated Widgets', debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, accentColor: Colors.redAccent, ), home: FirstPage(), ); } }

Questo codice pulisce il file main.dart semplicemente aggiungendo semplici informazioni rilevanti per la creazione di una nuova app. La classe MyApp restituisce un oggetto: un widget MaterialApp , che fornisce la struttura di base per la creazione di app conformi a Material Design. Per rendere il codice più strutturato, crea due nuovi file dart all'interno della cartella lib : FirstPage.dart e Quotes.dart .

Il file FirstPage.dart. (Grande anteprima)

FirstPage.dart conterrà tutto il codice responsabile di tutti gli elementi visivi (widget) richiesti per la nostra app Quoted. Tutta l'animazione è gestita in questo file.

Nota : più avanti nell'articolo, tutti i frammenti di codice per ogni widget animato vengono aggiunti a questo file come figli del widget Scaffold. Per ulteriori informazioni, questo esempio su GitHub potrebbe essere utile.

Inizia aggiungendo il codice seguente a FirstPage.dart . Questo è il codice parziale a cui verranno aggiunte altre cose in seguito.

 import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } } import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } } import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } } import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } }
Il file Quotes.dart. (Grande anteprima)

Il file Quotes.dart contiene un elenco di tutte le citazioni hardcoded. Un punto da notare qui è che l'elenco è un oggetto statico. Ciò significa che può essere utilizzato in altri luoghi senza creare un nuovo oggetto della classe Quotes. Questo è scelto in base alla progettazione, poiché l'elenco sopra funge semplicemente da utilità.

Aggiungi il seguente codice a questo file:

 class Quotes { static const quotesArray = [ "Good, better, best. Never let it rest. 'Til your good is better and your better is best", "It does not matter how slowly you go as long as you do not stop.", "Only I can change my life. No one can do it for me." ]; }

Lo scheletro del progetto è ora pronto, quindi arricchiamo un po' di più Quoted.

AnimatedOpacity

Per dare un tocco personale all'app, sarebbe bello conoscere il nome dell'utente, quindi chiediamolo e mostriamo un pulsante successivo. Fino a quando l'utente non inserisce il proprio nome, questo pulsante è nascosto e verrà visualizzato con grazia quando viene assegnato un nome. Abbiamo bisogno di una sorta di animazione di visibilità per il pulsante, ma esiste un widget per questo? Si C'è.

Immettere AnimatedOpacity . Questo widget si basa sul widget Opacità aggiungendo il supporto per l'animazione implicita. Come lo usiamo? Ricorda il nostro scenario: dobbiamo mostrare un pulsante successivo con visibilità animata. Avvolgiamo il widget del pulsante all'interno del widget AnimatedOpacity , inseriamo alcuni valori appropriati e aggiungiamo una condizione per attivare l'animazione e Flutter può gestire il resto.

 _getAnimatedOpacityButton() { return AnimatedOpacity( duration: Duration(seconds: 1), reverseDuration: Duration(seconds: 1), curve: Curves.easeInOut, opacity: showNextButton ? 1 : 0, child: _getButton(), ); }
Animazione dell'opacità del pulsante successivo. (Grande anteprima)

Il widget AnimatedOpacity ha due proprietà obbligatorie:

  • opacity
    Un valore di 1 significa completamente visibile; 0 (zero) significa nascosto. Durante l'animazione, Flutter interpola i valori tra questi due estremi. Puoi vedere come viene posizionata una condizione per modificare la visibilità, attivando così l'animazione.
  • child
    Il widget figlio che avrà la sua visibilità animata.

Ora dovresti capire quanto sia davvero semplice aggiungere un'animazione di visibilità con il widget implicito. E tutti questi widget seguono le stesse linee guida e sono facili da usare. Passiamo al prossimo.

AnimatedCrossFade

Abbiamo il nome dell'utente, ma il widget è ancora in attesa di input. Nel passaggio precedente, quando l'utente inserisce il proprio nome, viene visualizzato il pulsante successivo. Ora, quando l'utente preme il pulsante, voglio smettere di accettare l'input e mostrare il nome inserito. Ci sono molti modi per farlo, ovviamente, ma forse possiamo nascondere il widget di input e mostrare un widget di testo non modificabile. Proviamolo usando il widget AnimatedCrossFade .

Questo widget richiede due figli, poiché il widget si dissolve in modo incrociato tra loro in base a determinate condizioni. Una cosa importante da tenere a mente durante l'utilizzo di questo widget è che entrambi i bambini dovrebbero avere la stessa larghezza. Se l'altezza è diversa, il widget più alto viene ritagliato dal basso. In questo scenario, verranno utilizzati due widget come figli: input e label.

 _getAnimatedCrossfade() { return AnimatedCrossFade( duration: Duration(seconds: 1), alignment: Alignment.center, reverseDuration: Duration(seconds: 1), firstChild: _getNameInputWidget(), firstCurve: Curves.easeInOut, secondChild: _getNameLabelWidget(), secondCurve: Curves.easeInOut, crossFadeState: showNameLabel ? CrossFadeState.showSecond : CrossFadeState.showFirst, ); }
Dissolvenza incrociata tra il widget di input e il widget del nome. (Grande anteprima)

Questo widget richiede un diverso insieme di parametri obbligatori:

  • crossFadeState
    Questo stato determina quale bambino mostrare.
  • firstChild
    Specifica il primo figlio per questo widget.
  • secondChild
    Specifica il secondo figlio.

AnimatedAlign

A questo punto, l'etichetta del nome è posizionata al centro dello schermo. Sembrerà molto meglio in alto, poiché abbiamo bisogno del centro dello schermo per mostrare le virgolette. In poche parole, l'allineamento del widget dell'etichetta del nome dovrebbe essere modificato dal centro verso l'alto. E non sarebbe bello animare questo cambio di allineamento insieme alla precedente animazione di dissolvenza incrociata? Facciamolo.

Come sempre, per raggiungere questo obiettivo possono essere utilizzate diverse tecniche. Poiché il widget dell'etichetta del nome è già allineato al centro, animare il suo allineamento sarebbe molto più semplice che manipolare i valori superiore e sinistro del widget. Il widget AnimatedAlign è perfetto per questo lavoro.

Per avviare questa animazione, è necessario un trigger. L'unico scopo di questo widget è quello di animare la modifica dell'allineamento, quindi ha solo alcune proprietà: aggiungere un figlio, impostarne l'allineamento, attivare la modifica dell'allineamento e il gioco è fatto.

 _getAnimatedAlignWidget() { return AnimatedAlign( duration: Duration(seconds: 1), curve: Curves.easeInOut, alignment: alignTop ? Alignment.topLeft : Alignment.center, child: _getAnimatedCrossfade(), ); }
Animazione di allineamento del widget del nome. (Grande anteprima)

Ha solo due proprietà obbligatorie:

  • bambino:
    Il bambino il cui allineamento verrà modificato.
  • allineamento:
    Valore di allineamento richiesto.

Questo widget è davvero semplice ma i risultati sono eleganti. Inoltre, abbiamo visto con quanta facilità possiamo utilizzare due diversi widget animati per creare un'animazione più complessa. Questa è la bellezza dei widget animati.

AnimatedPadding

Ora abbiamo il nome dell'utente in alto, animato in modo fluido senza troppi sforzi, utilizzando diversi tipi di widget animati. Aggiungiamo un saluto, "Ciao", prima del nome. L'aggiunta di un widget di testo con il valore "Ciao", in alto, lo farà sovrapporre al widget di testo di saluto, simile all'immagine qui sotto.

I widget di saluto e nome si sovrappongono. (Grande anteprima)

E se il widget di testo del nome avesse un riempimento a sinistra? Aumentare il padding sinistro funzionerà sicuramente, ma aspetta: possiamo aumentare il padding con qualche animazione? Sì, ed è quello che fa AnimatedPadding . Per rendere tutto questo molto più bello, facciamo in modo che il widget di testo dei saluti si sbiadisca e che il riempimento del widget di testo del nome aumenti allo stesso tempo.

 _getAnimatedPaddingWidget() { return AnimatedPadding( duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn, padding: increaseLeftPadding ? EdgeInsets.only(left: 28.0) : EdgeInsets.only(left: 0), child: _getAnimatedCrossfade(), ); }

Poiché l'animazione sopra dovrebbe verificarsi solo dopo che l'allineamento animato precedente è stato completato, è necessario ritardare l'attivazione di questa animazione. Divagando brevemente dall'argomento, questo è un buon momento per parlare di un meccanismo popolare per aggiungere ritardo. Flutter fornisce diverse di queste tecniche, ma il costruttore Future.delayed è uno degli approcci più semplici, più puliti e più leggibili. Ad esempio, per eseguire un pezzo di codice dopo 1 secondo:

 Future.delayed(Duration(seconds: 1), (){ sum = a + b; // This sum will be calculated after 1 second. print(sum); });

Poiché la durata del ritardo è già nota (calcolata dalla durata dell'animazione precedente), l'animazione può essere attivata dopo questo intervallo.

 // Showing “Hi” after 1 second - greetings visibility trigger. _showGreetings() { Future.delayed(Duration(seconds: 1), () { setState(() { showGreetings = true; }); }); } // Increasing the padding for name label widget after 1 second - increase padding trigger. _increaseLeftPadding() { Future.delayed(Duration(seconds: 1), () { setState(() { increaseLeftPadding = true; }); }); }
Animazione di riempimento del widget del nome. (Grande anteprima)

Questo widget ha solo due proprietà obbligatorie:

  • child
    Il bambino all'interno di questo widget, a cui verrà applicato il riempimento.
  • padding
    La quantità di spazio da aggiungere.

AnimatedSize

Oggi, qualsiasi app con un qualche tipo di animazione includerà lo zoom avanti o indietro dei componenti visivi per attirare l'attenzione dell'utente (comunemente chiamata animazione di ridimensionamento). Perché non usare la stessa tecnica qui? Possiamo mostrare all'utente una citazione motivazionale che ingrandisce dal centro dello schermo. Lascia che ti presenti il ​​widget AnimatedSize , che abilita gli effetti di zoom avanti e indietro, controllati modificando la dimensione del suo figlio.

Questo widget è un po' diverso dagli altri quando si tratta dei parametri richiesti. Abbiamo bisogno di quello che Flutter chiama "Ticker". Flutter ha un metodo per far sapere agli oggetti ogni volta che viene attivato un nuovo evento frame. Può essere pensato come qualcosa che invia un segnale dicendo: "Fallo ora! … Fallo ora! … Fallo ora! …”

Il widget AnimatedSize richiede una proprietà, vsync , che accetta un provider di ticker. Il modo più semplice per ottenere un provider di ticker è aggiungere un Mixin alla classe. Esistono due implementazioni di base del provider di ticker: SingleTickerProviderStateMixin , che fornisce un singolo ticker; e TickerProviderStateMixin , che ne fornisce diversi.

L'implementazione predefinita di un Ticker viene utilizzata per contrassegnare i fotogrammi di un'animazione. In questo caso, quest'ultimo è impiegato. Maggiori informazioni sui mixin.

 // Helper method to create quotes card widget. _getQuoteCardWidget() { return Card( color: Colors.green, elevation: 8.0, child: _getAnimatedSizeWidget(), ); } // Helper method to create animated size widget and set its properties. _getAnimatedSizeWidget() { return AnimatedSize( duration: Duration(seconds: 1), curve: Curves.easeInOut, vsync: this, child: _getQuoteContainer(), ); } // Helper method to create the quotes container widget with different sizes. _getQuoteContainer() { return Container( height: showQuoteCard ? 100 : 0, width: showQuoteCard ? screenWidth - 32 : 0, child: Center( child: Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: Text(quote, style: TextStyle(color: Colors.white, fontWeight: FontWeight.w400, fontSize: 14),), ), ), ); } // Trigger used to show the quote card widget. _showQuote() { Future.delayed(Duration(seconds: 2), () { setState(() { showQuoteCard = true; }); }); }
Animazione in scala del widget virgolette. (Grande anteprima)

Proprietà obbligatorie per questo widget:

  • vsync
    Il provider di ticker richiesto per coordinare l'animazione e le modifiche ai fotogrammi.<
  • child
    Il bambino la cui taglia cambia sarà animato.

L'animazione di zoom avanti e indietro è ora facilmente domata.

AnimatedPositioned

Grande! Le virgolette ingrandiscono dal centro per attirare l'attenzione dell'utente. E se fosse scivolato verso l'alto dal basso durante l'ingrandimento? Proviamolo. Questo movimento implica giocare con la posizione del widget preventivo e animare i cambiamenti nelle proprietà della posizione. AnimatedPositioned è il candidato perfetto.

Questo widget trasferisce automaticamente la posizione del bambino per una determinata durata ogni volta che la posizione specificata cambia. Un punto da notare: funziona solo se il suo widget padre è uno "Stack". Questo widget è piuttosto semplice e diretto da usare. Vediamo.

 // Helper method to create the animated positioned widget. // With position changes based on “showQuoteCard” flag. _getAnimatedPositionWidget() { return AnimatedPositioned( duration: Duration(seconds: 1), curve: Curves.easeInOut, child: _getQuoteCardWidget(), top: showQuoteCard ? screenHeight/2 - 100 : screenHeight, left: !showQuoteCard ? screenWidth/2 : 12, ); }
Posizione con animazione in scala delle virgolette. (Grande anteprima)

Questo widget ha una sola proprietà obbligatoria:

  • child
    Il widget la cui posizione verrà modificata.

Se la dimensione del bambino non dovrebbe cambiare insieme alla sua posizione, un'alternativa più performativa a questo widget sarebbe SlideTransition .

Ecco la nostra animazione completa:

Tutti i widget animati insieme. (Grande anteprima)

Conclusione

Le animazioni sono parte integrante dell'esperienza dell'utente. Le app statiche o le app con animazioni stravaganti non solo riducono la fidelizzazione degli utenti, ma anche la reputazione di uno sviluppatore di fornire risultati.

Oggi, le app più popolari hanno una sorta di animazione sottile per deliziare gli utenti. Il feedback animato alle richieste degli utenti può anche coinvolgerli per esplorare di più. Flutter offre molte funzionalità per lo sviluppo multipiattaforma, incluso un supporto completo per animazioni fluide e reattive.

Flutter ha un ottimo supporto per i plug-in che ci consente di utilizzare le animazioni di altri sviluppatori. Ora che è maturato fino alla versione 1.9, con così tanto amore da parte della community, Flutter è destinato a migliorare in futuro. Direi che ora è un ottimo momento per imparare Flutter!

Ulteriori risorse

  • "Widget di animazione e movimento", Flutter Docs
  • "Introduzione alle animazioni", Flutter Docs
  • Flutter Codelab

Nota del redattore : un enorme ringraziamento ad Ahmad Awais per il suo aiuto nella revisione di questo articolo.