Gestisci l'animazione e l'animazione dei widget con MotionLayout

Prova Compose
Jetpack Compose è il toolkit per la UI consigliato per Android. Scopri come utilizzare le animazioni in Compose.

MotionLayout è un tipo di layout che ti aiuta a gestire il movimento e l'animazione dei widget nella tua app. MotionLayout è una sottoclasse di ConstraintLayout e si basa sulle sue ricche funzionalità di layout. Nell'ambito della libreria ConstraintLayout, MotionLayout è disponibile come libreria di supporto.

MotionLayout colma il divario tra le transizioni di layout e la gestione complessa del movimento, offrendo un mix di funzionalità tra il framework di animazione delle proprietà, TransitionManager e CoordinatorLayout.

Figura 1. Movimento di base controllato dal tocco.

Oltre a descrivere le transizioni tra i layout, MotionLayout ti consente di animare qualsiasi proprietà del layout. Inoltre, supporta intrinsecamente le transizioni ricercabili. Ciò significa che puoi mostrare istantaneamente qualsiasi punto all'interno della transizione in base a una condizione, ad esempio l'input tocco. MotionLayout supporta anche i fotogrammi chiave, consentendo transizioni completamente personalizzate in base alle tue esigenze.

MotionLayout è completamente dichiarativo, il che significa che puoi descrivere qualsiasi transizione in XML, indipendentemente dalla sua complessità.

Note sul layout

MotionLayout è progettato per spostare, ridimensionare e animare gli elementi della UI con cui gli utenti interagiscono, come pulsanti e barre del titolo. Non utilizzare il movimento nella tua app come effetto speciale senza costi. Utilizzalo per aiutare gli utenti a capire cosa fa la tua app. Per saperne di più sulla progettazione dell'app con il movimento, consulta la sezione Comprendere il movimento di Material Design.

Inizia

Per iniziare a utilizzare MotionLayout nel tuo progetto, segui questi passaggi.

  1. Aggiungi la dipendenza ConstraintLayout:per utilizzare MotionLayout nel tuo progetto, aggiungi la dipendenza ConstraintLayout 2.0 al file build.gradle della tua app. Se utilizzi AndroidX, aggiungi la seguente dipendenza:

    Alla moda

    dependencies {
        implementation "androidx.constraintlayout:constraintlayout:2.2.1"
        // To use constraintlayout in compose
        implementation "androidx.constraintlayout:constraintlayout-compose:1.1.1"
    }

    Kotlin

    dependencies {
        implementation("androidx.constraintlayout:constraintlayout:2.2.1")
        // To use constraintlayout in compose
        implementation("androidx.constraintlayout:constraintlayout-compose:1.1.1")
    }
  2. Crea un file MotionLayout:MotionLayout è una sottoclasse di ConstraintLayout, quindi puoi trasformare qualsiasi ConstraintLayout esistente in un MotionLayout sostituendo il nome della classe nel file di risorse di layout, come mostrato negli esempi seguenti:

    AndroidX

    <!-- before: ConstraintLayout -->
    <androidx.constraintlayout.widget.ConstraintLayout .../>
    <!-- after: MotionLayout -->
    <androidx.constraintlayout.motion.widget.MotionLayout .../>
              

    Support Library

    <!-- before: ConstraintLayout -->
    <android.support.constraint.ConstraintLayout .../>
    <!-- after: MotionLayout -->
    <android.support.constraint.motion.MotionLayout .../>
              

    Ecco un esempio completo di un file MotionLayout, che definisce il layout mostrato nella figura 1:

    AndroidX

    <?xml version="1.0" encoding="utf-8"?>
    <!-- activity_main.xml -->
    <androidx.constraintlayout.motion.widget.MotionLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/motionLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutDescription="@xml/scene_01"
        tools:showPaths="true">
    
        <View
            android:id="@+id/button"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:background="@color/colorAccent"
            android:text="Button" />
    
    </androidx.constraintlayout.motion.widget.MotionLayout>
            

    Support Library

    <?xml version="1.0" encoding="utf-8"?>
    <!-- activity_main.xml -->
    <android.support.constraint.motion.MotionLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/motionLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutDescription="@xml/scene_01"
        tools:showPaths="true">
    
        <View
            android:id="@+id/button"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:background="@color/colorAccent"
            android:text="Button" />
    
    </android.support.constraint.motion.MotionLayout>
            
  3. Crea una MotionScene:nell'esempio precedente di MotionLayout, l'attributo app:layoutDescription fa riferimento a una scena di movimento. Una scena di movimento è un file di risorse XML. All'interno dell'elemento <MotionScene> radice, una scena di movimento contiene tutte le descrizioni di movimento per il layout corrispondente. Per mantenere separate le informazioni sul layout dalle descrizioni del movimento, ogni MotionLayout fa riferimento a una scena di movimento separata. Le definizioni nella scena di movimento hanno la precedenza su qualsiasi definizione simile in MotionLayout.

    Ecco un esempio di file di scena di movimento che descrive il movimento orizzontale di base nella figura 1:

    <?xml version="1.0" encoding="utf-8"?>
    <MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:motion="http://schemas.android.com/apk/res-auto">
    
        <Transition
            motion:constraintSetStart="@+id/start"
            motion:constraintSetEnd="@+id/end"
            motion:duration="1000">
            <OnSwipe
                motion:touchAnchorId="@+id/button"
                motion:touchAnchorSide="right"
                motion:dragDirection="dragRight" />
        </Transition>
    
        <ConstraintSet android:id="@+id/start">
            <Constraint
                android:id="@+id/button"
                android:layout_width="64dp"
                android:layout_height="64dp"
                android:layout_marginStart="8dp"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintStart_toStartOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </ConstraintSet>
    
        <ConstraintSet android:id="@+id/end">
            <Constraint
                android:id="@+id/button"
                android:layout_width="64dp"
                android:layout_height="64dp"
                android:layout_marginEnd="8dp"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintEnd_toEndOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </ConstraintSet>
    
    </MotionScene>
        

    Tieni presente quanto segue:

    • <Transition> contiene la definizione di base del movimento.

      • motion:constraintSetStart e motion:constraintSetEnd sono riferimenti agli endpoint del movimento. Questi endpoint sono definiti negli elementi <ConstraintSet> più avanti nella scena di movimento.

      • motion:duration specifica il numero di millisecondi necessari per completare il movimento.

    • <OnSwipe> ti consente di creare un controllo touch per il movimento.

      • motion:touchAnchorId si riferisce alla visualizzazione che l'utente può scorrere e trascinare.

      • motion:touchAnchorSide indica che la visualizzazione viene trascinata dal lato destro.

      • motion:dragDirection si riferisce alla direzione di avanzamento del trascinamento. Ad esempio, motion:dragDirection="dragRight" indica che l'avanzamento aumenta man mano che la visualizzazione viene trascinata verso destra.

    • <ConstraintSet> è dove definisci i vari vincoli che descrivono il movimento. In questo esempio, viene definito un <ConstraintSet> per ogni endpoint del movimento. Questi endpoint sono centrati verticalmente utilizzando app:layout_constraintTop_toTopOf="parent" e app:layout_constraintBottom_toBottomOf="parent". Orizzontalmente, gli endpoint si trovano all'estrema sinistra e destra dello schermo.

    Per un'analisi più dettagliata dei vari elementi supportati da una scena di movimento, consulta gli esempi di MotionLayout.

Attributi interpolati

All'interno di un file di scena di movimento, gli elementi ConstraintSet possono contenere attributi aggiuntivi che vengono interpolati durante la transizione. Oltre a posizione e limiti, i seguenti attributi vengono interpolati da MotionLayout:

  • alpha
  • visibility
  • elevation
  • rotation, rotationX e rotationY
  • translationX, translationY e translationZ
  • scaleX, scaleY

Attributi personalizzati

All'interno di un <Constraint>, puoi utilizzare l'elemento <CustomAttribute> per specificare una transizione per gli attributi che non sono semplicemente correlati alla posizione o agli attributi View.

<Constraint
    android:id="@+id/button" ...>
    <CustomAttribute
        motion:attributeName="backgroundColor"
        motion:customColorValue="#D81B60"/>
</Constraint>

Un <CustomAttribute> contiene due attributi propri:

  • motion:attributeName è obbligatorio e deve corrispondere a un oggetto con metodi getter e setter. I metodi getter e setter devono corrispondere a un pattern specifico. Ad esempio, backgroundColor è supportato, poiché la vista ha metodi getBackgroundColor() e setBackgroundColor() sottostanti.
  • L'altro attributo che devi fornire dipende dal tipo di valore. Scegli tra i seguenti tipi supportati:
    • motion:customColorValue per i colori
    • motion:customIntegerValue per i numeri interi
    • motion:customFloatValue per i float
    • motion:customStringValue per le stringhe
    • motion:customDimension per le dimensioni
    • motion:customBoolean per i valori booleani

Quando specifichi un attributo personalizzato, definisci i valori dell'endpoint sia nell'elemento <ConstraintSet> iniziale che in quello finale.

Cambiare il colore dello sfondo

Riprendendo l'esempio precedente, supponiamo che tu voglia che i colori della visualizzazione cambino come parte del suo movimento, come mostrato nella figura 2.

Figura 2. La visualizzazione cambia colore dello sfondo mentre si sposta.

Aggiungi un elemento <CustomAttribute> a ogni elemento ConstraintSet, come mostrato nello snippet di codice seguente:

<ConstraintSet android:id="@+id/start">
    <Constraint
        android:id="@+id/button"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_marginStart="8dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintStart_toStartOf="parent"
        motion:layout_constraintTop_toTopOf="parent">
        <CustomAttribute
            motion:attributeName="backgroundColor"
            motion:customColorValue="#D81B60" />
    </Constraint>
</ConstraintSet>

<ConstraintSet android:id="@+id/end">
    <Constraint
        android:id="@+id/button"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_marginEnd="8dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintEnd_toEndOf="parent"
        motion:layout_constraintTop_toTopOf="parent">
        <CustomAttribute
            motion:attributeName="backgroundColor"
            motion:customColorValue="#9999FF" />
    </Constraint>
</ConstraintSet>

Attributi MotionLayout aggiuntivi

Oltre agli attributi nell'esempio precedente, MotionLayout ha altri attributi che potresti voler specificare:

  • app:applyMotionScene="boolean" indica se applicare la scena di movimento. Il valore predefinito di questo attributo è true.
  • app:showPaths="boolean" indica se mostrare le traiettorie di movimento durante l'esecuzione del movimento. Il valore predefinito di questo attributo è false.
  • app:progress="float" consente di specificare esplicitamente l'avanzamento della transizione. Puoi utilizzare qualsiasi valore in rappresentazione in virgola mobile da 0 (l'inizio della transizione) a 1 (la fine della transizione).
  • app:currentState="reference" ti consente di specificare un ConstraintSet specifico.
  • app:motionDebug ti consente di visualizzare informazioni di debug aggiuntive sul movimento. I valori possibili sono "SHOW_PROGRESS", "SHOW_PATH" o "SHOW_ALL".

Risorse aggiuntive

Per maggiori informazioni su MotionLayout, consulta le seguenti risorse: