Animazione delle modifiche al layout mediante una transizione

Prova la modalità Scrivi
Jetpack Compose è il toolkit dell'interfaccia utente consigliato per Android. Scopri come utilizzare le animazioni in Compose.

Il framework di transizione di Android consente di animare tutti i tipi di movimento nell'interfaccia utente fornendo i layout iniziale e finale. Puoi selezionare il tipo di animazione desiderato, ad esempio applicare una dissolvenza delle visualizzazioni in entrata o in uscita o modificare le dimensioni della vista, e il framework di transizione determina come animarsi dal layout iniziale a quello finale.

Il framework di transizione include le seguenti funzionalità:

  • Animazioni a livello di gruppo: applicano effetti di animazione a tutte le visualizzazioni di una gerarchia delle visualizzazioni.
  • Animazioni integrate: utilizza animazioni predefinite per gli effetti comuni, come la dissolvenza in uscita o il movimento.
  • Supporto dei file di risorse: carica le gerarchie di visualizzazioni e le animazioni integrate dai file di risorse di layout.
  • Callback del ciclo di vita: ricevono callback che forniscono il controllo sull'animazione e sul processo di modifica della gerarchia.

Per un esempio di codice che si anima tra le modifiche al layout, consulta BasicTransizione.

La procedura di base per animare tra due layout è la seguente:

  1. Crea un oggetto Scene per i layout iniziali e finali. Tuttavia, la scena del layout iniziale spesso viene determinata automaticamente dal layout corrente.
  2. Crea un oggetto Transition per definire il tipo di animazione che vuoi.
  3. Richiama TransitionManager.go() e il sistema esegue l'animazione per scambiare i layout.

Il diagramma nella figura 1 illustra la relazione tra i layout, le scene, la transizione e l'animazione finale.

Figura 1. Illustrazione di base di come il framework di transizione crea un'animazione.

Creare una scena

Le scene memorizzano lo stato di una gerarchia delle viste, incluse tutte le viste e i relativi valori delle proprietà. Il framework delle transizioni può eseguire animazioni tra una scena iniziale e una finale.

Puoi creare le scene da un file di risorse di layout o da un gruppo di viste nel codice. Tuttavia, la scena iniziale per la transizione viene spesso determinata automaticamente dall'interfaccia utente corrente.

Una scena può anche definire le proprie azioni che vengono eseguite quando cambi scena. Questa funzionalità è utile per ripulire le impostazioni della vista dopo la transizione a una scena.

Creare una scena da una risorsa di layout

Puoi creare un'istanza Scene direttamente da un file di risorse di layout. Utilizza questa tecnica quando la gerarchia di visualizzazione nel file è per lo più statica. La scena risultante rappresenta lo stato della gerarchia delle visualizzazioni al momento della creazione dell'istanza Scene. Se modifichi la gerarchia di visualizzazione, ricrea la scena. Il framework crea la scena dall'intera gerarchia di visualizzazioni nel file. Non puoi creare una scena da una parte di un file di layout.

Per creare un'istanza Scene da un file di risorse di layout, recupera la radice della scena dal layout come ViewGroup. Quindi, chiama la funzione Scene.getSceneForLayout() con la radice della scena e l'ID risorsa del file di layout che contiene la gerarchia di visualizzazione della scena.

Definisci i layout per le scene

Gli snippet di codice riportati nel resto di questa sezione mostrano come creare due scene diverse con lo stesso elemento principale della scena. Inoltre, gli snippet mostrano che è possibile caricare più oggetti Scene non correlati senza lasciare intendere che siano correlati l'uno all'altro.

L'esempio è costituito dalle seguenti definizioni di layout:

  • Il layout principale di un'attività con un'etichetta di testo e un elemento secondario FrameLayout.
  • Un elemento ConstraintLayout per la prima scena con due campi di testo.
  • Un ConstraintLayout per la seconda scena con gli stessi due campi di testo in ordine diverso.

L'esempio è progettato in modo che tutta l'animazione si verifichi all'interno del layout secondario del layout principale dell'attività. L'etichetta di testo nel layout principale rimane statica.

Il layout principale dell'attività è definito come segue:

res/layout/activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/master_layout">
    <TextView
        android:id="@+id/title"
        ...
        android:text="Title"/>
    <FrameLayout
        android:id="@+id/scene_root">
        <include layout="@layout/a_scene" />
    </FrameLayout>
</LinearLayout>

Questa definizione di layout contiene un campo di testo e un elemento secondario FrameLayout per la radice della scena. Il layout della prima scena è incluso nel file di layout principale. Ciò consente all'app di visualizzarla come parte dell'interfaccia utente iniziale e di caricarla in una scena, dato che il framework può caricare solo un intero file di layout in una scena.

Il layout della prima scena è definito come segue:

res/layout/a_scene.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/scene_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    
</androidx.constraintlayout.widget.ConstraintLayout>

Il layout della seconda scena contiene gli stessi due campi di testo, con gli stessi ID, in ordine diverso. Si definisce come segue:

res/layout/another_scene.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/scene_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    
</androidx.constraintlayout.widget.ConstraintLayout>

Genera scene dai layout

Dopo aver creato le definizioni dei due layout dei vincoli, puoi ottenere una scena per ciascuno di essi. Questo consente di passare tra le due configurazioni UI. Per ottenere una scena, è necessario un riferimento alla radice della scena e all'ID risorsa di layout.

Il seguente snippet di codice mostra come ottenere un riferimento alla radice della scena e creare due oggetti Scene dai file di layout:

Kotlin

val sceneRoot: ViewGroup = findViewById(R.id.scene_root)
val aScene: Scene = Scene.getSceneForLayout(sceneRoot, R.layout.a_scene, this)
val anotherScene: Scene = Scene.getSceneForLayout(sceneRoot, R.layout.another_scene, this)

Java

Scene aScene;
Scene anotherScene;

// Create the scene root for the scenes in this app.
sceneRoot = (ViewGroup) findViewById(R.id.scene_root);

// Create the scenes.
aScene = Scene.getSceneForLayout(sceneRoot, R.layout.a_scene, this);
anotherScene =
    Scene.getSceneForLayout(sceneRoot, R.layout.another_scene, this);

Nell'app ora sono presenti due oggetti Scene basati sulle gerarchie delle viste. Entrambe le scene utilizzano la radice della scena definita dall'elemento FrameLayout in res/layout/activity_main.xml.

Crea una scena nel codice

Puoi anche creare un'istanza Scene nel codice da un oggetto ViewGroup. Utilizza questa tecnica quando modifichi le gerarchie delle viste direttamente nel codice o quando le generi dinamicamente.

Per creare una scena da una gerarchia di visualizzazioni nel codice, utilizza il costruttore Scene(sceneRoot, viewHierarchy). Chiamare questo costruttore equivale a chiamare la funzione Scene.getSceneForLayout() quando hai già aumentato in modo artificioso un file di layout.

Lo snippet di codice seguente mostra come creare un'istanza Scene dall'elemento principale della scena e dalla gerarchia di visualizzazione della scena nel codice:

Kotlin

val sceneRoot = someLayoutElement as ViewGroup
val viewHierarchy = someOtherLayoutElement as ViewGroup
val scene: Scene = Scene(sceneRoot, viewHierarchy)

Java

Scene mScene;

// Obtain the scene root element.
sceneRoot = (ViewGroup) someLayoutElement;

// Obtain the view hierarchy to add as a child of
// the scene root when this scene is entered.
viewHierarchy = (ViewGroup) someOtherLayoutElement;

// Create a scene.
mScene = new Scene(sceneRoot, mViewHierarchy);

Crea azioni scena

Il framework consente di definire azioni personalizzate per le scene che il sistema esegue quando entra o esce da una scena. In molti casi, non è necessario definire azioni personalizzate per le scene, poiché il framework anima automaticamente il cambiamento tra le scene.

Le azioni scena sono utili per gestire questi casi:

  • Per animare le visualizzazioni che non si trovano nella stessa gerarchia. Puoi animare le visualizzazioni per la scena iniziale e finale utilizzando le azioni di uscita e di ingresso.
  • Per animare le viste che non è possibile animare automaticamente nel framework di transizioni, ad esempio oggetti ListView. Per ulteriori informazioni, consulta la sezione sulle limitazioni.

Per fornire azioni personalizzate della scena, definisci le azioni come oggetti Runnable e passale alle funzioni Scene.setExitAction() o Scene.setEnterAction(). Il framework chiama la funzione setExitAction() nella scena iniziale prima dell'esecuzione dell'animazione di transizione e la funzione setEnterAction() nella scena finale dopo l'esecuzione dell'animazione di transizione.

Applicare una transizione

Il framework di transizione rappresenta lo stile di animazione tra le scene con un oggetto Transition. Puoi creare un'istanza di Transition utilizzando sottoclassi integrate, come AutoTransition e Fade, oppure definire la tua transizione. Quindi, puoi eseguire l'animazione tra le scene passando l'estremità Scene e Transition a TransitionManager.go().

Il ciclo di vita della transizione è simile al ciclo di vita dell'attività e rappresenta gli stati di transizione monitorati dal framework tra l'inizio e il completamento di un'animazione. In stati importanti del ciclo di vita, il framework richiama funzioni di callback che puoi implementare per regolare l'interfaccia utente in diverse fasi della transizione.

Crea una transizione

La sezione precedente mostra come creare scene che rappresentano lo stato di diverse gerarchie di visualizzazioni. Dopo aver definito la scena iniziale e quella finale tra le due, crea un oggetto Transition che definisca un'animazione. Il framework consente di specificare una transizione integrata in un file di risorse e di aumentarla nel codice, oppure di creare un'istanza di una transizione integrata direttamente nel codice.

Tabella 1. Tipi di transizione integrati.

Classe Tagga Effetto
AutoTransition <autoTransition/> Transizione predefinita. Dissolvenza in uscita, sposta, ridimensiona e dissolvenza nelle visualizzazioni in questo ordine.
ChangeBounds <changeBounds/> Sposta e ridimensiona le visualizzazioni.
ChangeClipBounds <changeClipBounds/> Acquisisce View.getClipBounds() prima e dopo il cambio di scena e anima le modifiche durante la transizione.
ChangeImageTransform <changeImageTransform/> Cattura la matrice di ImageView prima e dopo il cambio di scena e la anima durante la transizione.
ChangeScroll <changeScroll/> Acquisisce le proprietà di scorrimento dei target prima e dopo il cambiamento della scena e anima le modifiche.
ChangeTransform <changeTransform/> Acquisisce la scala e la rotazione delle visualizzazioni prima e dopo il cambio di scena e anima le modifiche durante la transizione.
Explode <explode/> Tiene traccia delle variazioni di visibilità delle visualizzazioni di destinazione nelle scene iniziali e finali e sposta le visualizzazioni all'interno o all'esterno della scena.
Fade <fade/> fade_in dissolvenza nelle visualizzazioni.
fade_out visualizzazioni in dissolvenza.
fade_in_out (valore predefinito) esegue una fade_out seguita da una fade_in.
Slide <slide/> Tiene traccia delle variazioni di visibilità delle visualizzazioni di destinazione nelle scene iniziali e finali e sposta le visualizzazioni all'interno o all'esterno della scena da uno dei bordi.

Crea un'istanza di transizione da un file di risorse

Questa tecnica consente di modificare la definizione della transizione senza modificare il codice dell'attività. Questa tecnica è utile anche per separare definizioni di transizione complesse dal codice dell'applicazione, come mostrato nella sezione relativa alla specificare più transizioni.

Per specificare una transizione integrata in un file di risorse:

  • Aggiungi la directory res/transition/ al progetto.
  • Crea un nuovo file di risorse XML all'interno di questa directory.
  • Aggiungi un nodo XML per una delle transizioni integrate.

Ad esempio, il seguente file di risorse specifica la transizione Fade:

res/transition/fade_transition.xml

<fade xmlns:android="http://schemas.android.com/apk/res/android" />

Il seguente snippet di codice mostra come gonfiare un'istanza Transition all'interno dell'attività da un file di risorse:

Kotlin

var fadeTransition: Transition =
    TransitionInflater.from(this)
                      .inflateTransition(R.transition.fade_transition)

Java

Transition fadeTransition =
        TransitionInflater.from(this).
        inflateTransition(R.transition.fade_transition);

Crea un'istanza di transizione nel codice

Questa tecnica è utile per creare oggetti di transizione in modo dinamico se modifichi l'interfaccia utente nel codice e per creare semplici istanze di transizione integrate con pochi parametri o nessuno.

Per creare un'istanza di una transizione integrata, richiama uno dei costruttori pubblici nelle sottoclassi della classe Transition. Ad esempio, il seguente snippet di codice crea un'istanza della transizione Fade:

Kotlin

var fadeTransition: Transition = Fade()

Java

Transition fadeTransition = new Fade();

Applicare una transizione

In genere, applichi una transizione per passare tra le diverse gerarchie di visualizzazioni in risposta a un evento, ad esempio a un'azione dell'utente. Prendiamo come esempio un'app di ricerca: quando l'utente inserisce un termine di ricerca e tocca il pulsante di ricerca, l'app assume una scena che rappresenta il layout dei risultati e viene applicata una transizione che attenua la dissolvenza del pulsante di ricerca e sbiadisce nei risultati di ricerca.

Per cambiare scena durante l'applicazione di una transizione in risposta a un evento nella tua attività, chiama la funzione di classe TransitionManager.go() con la scena finale e l'istanza di transizione da utilizzare per l'animazione, come mostrato nel seguente snippet:

Kotlin

TransitionManager.go(endingScene, fadeTransition)

Java

TransitionManager.go(endingScene, fadeTransition);

Il framework modifica la gerarchia di visualizzazione all'interno della radice della scena con la gerarchia di visualizzazione della scena finale durante l'esecuzione dell'animazione specificata dall'istanza di transizione. La scena iniziale è la scena finale dell'ultima transizione. Se non esiste una transizione precedente, la scena iniziale viene determinata automaticamente dallo stato attuale dell'interfaccia utente.

Se non specifichi un'istanza di transizione, il gestore di transizione può applicare una transizione automatica che offra un comportamento ragionevole per la maggior parte delle situazioni. Per ulteriori informazioni, consulta il riferimento API per la classe TransitionManager.

Scegli visualizzazioni target specifiche

Per impostazione predefinita, il framework applica le transizioni a tutte le visualizzazioni nelle scene iniziale e finale. In alcuni casi, potresti voler applicare un'animazione solo a un sottoinsieme di visualizzazioni di una scena. Il framework ti consente di selezionare viste specifiche da animare. Ad esempio, il framework non supporta l'animazione delle modifiche agli oggetti ListView, quindi non provare ad animarle durante una transizione.

Ogni visualizzazione animata dalla transizione è denominata target. Puoi selezionare solo i target che fanno parte della gerarchia di visualizzazione associata a una scena.

Per rimuovere una o più viste dall'elenco dei target, chiama il metodo removeTarget() prima di iniziare la transizione. Per aggiungere solo le visualizzazioni specificate all'elenco dei target, chiama la funzione addTarget(). Per maggiori informazioni, consulta il riferimento API per la classe Transition.

Specificare più transizioni

Per ottenere il massimo impatto da un'animazione, abbinala al tipo di modifiche che si verificano tra le scene. Ad esempio, se rimuovi alcune visualizzazioni e aggiungine altre da una scena all'altra, un'animazione con dissolvenza in uscita o in dissolvenza indica che alcune visualizzazioni non sono più disponibili. Se sposti le visualizzazioni in punti diversi dello schermo, è meglio animare il movimento in modo che gli utenti notino la nuova posizione delle visualizzazioni.

Non è necessario scegliere una sola animazione perché il framework delle transizioni ti consente di combinare gli effetti dell'animazione in un set di transizioni che contiene un gruppo di transizioni singole integrate o personalizzate.

Per definire un set di transizione da una raccolta di transizioni in XML, crea un file di risorse nella directory res/transitions/ ed elenca le transizioni nell'elemento TransitionSet. Ad esempio, lo snippet seguente mostra come specificare un set di transizione con lo stesso comportamento della classe AutoTransition:

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="sequential">
    <fade android:fadingMode="fade_out" />
    <changeBounds />
    <fade android:fadingMode="fade_in" />
</transitionSet>

Per gonfiare la transizione impostata in un oggetto TransitionSet nel codice, chiama la funzione TransitionInflater.from() nell'attività. La classe TransitionSet si estende dalla classe Transition, quindi puoi utilizzarla con un gestore di transizione come qualsiasi altra istanza Transition.

Applicare una transizione senza scene

Modificare le gerarchie delle viste non è l'unico modo per modificare l'interfaccia utente. Puoi anche apportare modifiche aggiungendo, modificando e rimuovendo le viste secondarie all'interno della gerarchia corrente.

Ad esempio, puoi implementare un'interazione di ricerca con un singolo layout. Inizia con il layout che mostra un campo di ricerca e un'icona di ricerca. Per cambiare l'interfaccia utente in modo da mostrare i risultati, rimuovi il pulsante di ricerca quando l'utente lo tocca chiamando la funzione ViewGroup.removeView() e aggiungi i risultati di ricerca chiamando la funzione ViewGroup.addView().

Puoi utilizzare questo approccio se l'alternativa è avere due gerarchie quasi identiche. Anziché creare e gestire due file di layout separati per una piccola differenza nell'interfaccia utente, puoi avere un file di layout con una gerarchia di viste da modificare nel codice.

Se apporti modifiche alla gerarchia di visualizzazione corrente in questo modo, non è necessario creare una scena. Puoi invece creare e applicare una transizione tra due stati di una gerarchia di viste utilizzando una transizione ritardata. Questa funzionalità del framework delle transizioni inizia con lo stato corrente della gerarchia delle viste, registra le modifiche apportate alle sue viste e applica una transizione che anima le modifiche quando il sistema ridisegna l'interfaccia utente.

Per creare una transizione ritardata all'interno di una singola gerarchia di visualizzazione:

  1. Quando si verifica l'evento che attiva la transizione, chiama la funzione TransitionManager.beginDelayedTransition(), che fornisce la vista principale di tutte le viste che vuoi modificare e la transizione da utilizzare. Il framework archivia lo stato corrente delle viste secondarie e i relativi valori delle proprietà.
  2. Apporta le modifiche alle viste secondarie come richiesto dal tuo caso d'uso. Il framework registra le modifiche apportate alle viste secondarie e alle relative proprietà.
  3. Quando il sistema ridisegna l'interfaccia utente in base alle modifiche, il framework anima le variazioni tra lo stato originale e il nuovo stato.

L'esempio seguente mostra come animare l'aggiunta di una visualizzazione di testo a una gerarchia di visualizzazione utilizzando una transizione ritardata. Il primo snippet mostra il file di definizione del layout:

res/layout/activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/mainLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <EditText
        android:id="@+id/inputText"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />
    ...
</androidx.constraintlayout.widget.ConstraintLayout>

Lo snippet successivo mostra il codice che anima l'aggiunta della visualizzazione testo:

MainActivity

Kotlin

setContentView(R.layout.activity_main)
val labelText = TextView(this).apply {
    text = "Label"
    id = R.id.text
}
val rootView: ViewGroup = findViewById(R.id.mainLayout)
val mFade: Fade = Fade(Fade.IN)
TransitionManager.beginDelayedTransition(rootView, mFade)
rootView.addView(labelText)

Java

private TextView labelText;
private Fade mFade;
private ViewGroup rootView;
...
// Load the layout.
setContentView(R.layout.activity_main);
...
// Create a new TextView and set some View properties.
labelText = new TextView(this);
labelText.setText("Label");
labelText.setId(R.id.text);

// Get the root view and create a transition.
rootView = (ViewGroup) findViewById(R.id.mainLayout);
mFade = new Fade(Fade.IN);

// Start recording changes to the view hierarchy.
TransitionManager.beginDelayedTransition(rootView, mFade);

// Add the new TextView to the view hierarchy.
rootView.addView(labelText);

// When the system redraws the screen to show this update,
// the framework animates the addition as a fade in.

Definisci i callback del ciclo di vita della transizione

Il ciclo di vita della transizione è simile a quello delle attività. Rappresenta gli stati di transizione monitorati dal framework durante il periodo tra una chiamata alla funzione TransitionManager.go() e il completamento dell'animazione. In stati importanti del ciclo di vita, il framework richiama callback definiti dall'interfaccia TransitionListener.

I callback del ciclo di vita della transizione sono utili, ad esempio, per copiare il valore di una proprietà vista dalla gerarchia della vista iniziale a quella finale durante un cambio di scena. Non puoi semplicemente copiare il valore dalla sua visualizzazione iniziale a quella della gerarchia della vista finale perché la gerarchia della vista finale non viene ingrandita fino al completamento della transizione. Devi invece archiviare il valore in una variabile e poi copiarlo nella gerarchia della vista finale quando il framework ha completato la transizione. Per ricevere una notifica al completamento della transizione, implementa la funzione TransitionListener.onTransitionEnd() nell'attività.

Per maggiori informazioni, consulta il riferimento API per la classe TransitionListener.

Limitazioni

Questa sezione elenca alcune limitazioni note del framework di transizione:

  • Le animazioni applicate a una SurfaceView potrebbero non essere visualizzate correttamente. SurfaceView istanze vengono aggiornate da un thread non UI, pertanto gli aggiornamenti potrebbero non essere sincronizzati con le animazioni di altre viste.
  • Alcuni tipi di transizione specifici potrebbero non produrre l'effetto di animazione desiderato quando vengono applicati a una TextureView.
  • I corsi che estendono AdapterView, come ListView, gestiscono le visualizzazioni secondarie in modi incompatibili con il framework delle transizioni. Se provi ad animare una visualizzazione basata su AdapterView, il display del dispositivo potrebbe non rispondere.
  • Se provi a ridimensionare un elemento TextView con un'animazione, il testo viene visualizzato in una nuova posizione prima che l'oggetto venga ridimensionato completamente. Per evitare questo problema, non animare il ridimensionamento delle viste che contengono testo.