Android avanzato in Kotlin 03.2: animazione con MotionLayout

1. Prima di iniziare

Questo codelab fa parte del corso Advanced Android in Kotlin. Trarrai il massimo vantaggio da questo corso eseguendo i codelab in sequenza, ma non è obbligatorio. Tutti i codelab del corso sono elencati nella pagina di destinazione dei codelab Android in Kotlin avanzati.

MotionLayout è una raccolta che ti consente di aggiungere immagini di movimento alla tua app per Android. È basato su ConstraintLayout, e ti consente di animare tutto ciò che puoi creare utilizzando ConstraintLayout.

Puoi utilizzare MotionLayout per animare contemporaneamente posizione, dimensione, visibilità, alpha, colore, elevazione, rotazione e altri attributi di più viste. Utilizzando il codice XML dichiarativo puoi creare animazioni coordinate, che coinvolgono più viste, che sono difficili da ottenere nel codice.

Le animazioni sono un ottimo modo per migliorare l'esperienza di un'app. Puoi utilizzare le animazioni per:

  • Mostra modifiche: l'animazione tra gli stati consente all'utente di tenere traccia in modo naturale delle modifiche nella UI.
  • Attira l'attenzione: utilizza le animazioni per attirare l'attenzione sugli elementi importanti dell'interfaccia utente.
  • Crea splendidi design: i movimenti efficaci rendono le app più eleganti.

Prerequisiti

Questo codelab è stato progettato per gli sviluppatori con una certa esperienza di sviluppo Android. Prima di tentare di completare questo codelab, dovresti:

  • Sapere come creare un'app con un'attività, un layout di base ed eseguirla su un dispositivo o un emulatore usando Android Studio. Conosci ConstraintLayout. Leggi il codelab sul layout dei vincoli per saperne di più su ConstraintLayout.

Attività previste

  • Definisci un'animazione con ConstraintSets e MotionLayout
  • Creare animazioni in base a eventi di trascinamento
  • Cambia l'animazione con KeyPosition
  • Modifica attributi con KeyAttribute
  • Eseguire animazioni con codice
  • Applica l'animazione alle intestazioni comprimibili con MotionLayout

Che cosa ti serve

  • Android Studio 4.0 (l'editor MotionLayout funziona solo con questa versione di Android Studio).

2. Per iniziare

Per scaricare l'app di esempio, puoi:

... oppure clona il repository GitHub dalla riga di comando utilizzando questo comando:

$ git clone https://github.com/googlecodelabs/motionlayout.git

3. Creazione di animazioni con MotionLayout

Innanzitutto, devi creare un'animazione che sposti la visualizzazione dall'inizio superiore dello schermo all'estremità inferiore in risposta ai clic degli utenti.

Per creare un'animazione dal codice di base, sono necessari i seguenti elementi principali:

  • Un MotionLayout, che è una sottoclasse di ConstraintLayout. Devi specificare tutte le visualizzazioni da animate all'interno del tag MotionLayout.
  • Un MotionScene,, cioè un file XML che descrive un'animazione per MotionLayout.
  • Un elemento Transition, che fa parte di MotionScene che specifica la durata dell'animazione, l'attivatore e la modalità di spostamento delle visualizzazioni.
  • Un elemento ConstraintSet che specifica i vincoli start e end della transizione.

Esaminiamoli uno alla volta, a partire dal MotionLayout.

Passaggio 1: esplora il codice esistente

MotionLayout è una sottoclasse di ConstraintLayout, quindi supporta tutte le stesse funzionalità durante l'aggiunta dell'animazione. Per utilizzare MotionLayout, aggiungi una vista MotionLayout in cui useresti ConstraintLayout.

  1. In res/layout, apri activity_step1.xml.. Ecco un ConstraintLayout con un singolo ImageView di una stella, con una tinta applicata all'interno.

activity_step1.xml

<!-- initial code -->
<androidx.constraintlayout.widget.ConstraintLayout
       ...
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       >

   <ImageView
           android:id="@+id/red_star"
           ...
   />

</androidx.constraintlayout.motion.widget.MotionLayout>

Questo ConstraintLayout non ha vincoli, quindi se eseguissi l'app ora vedresti la visualizzazione a stella non vincolata, il che significa che l'elemento verrebbe posizionato in una posizione sconosciuta. Android Studio ti invierà un avviso relativo alla mancanza di vincoli.

Passaggio 2: converti in layout Movimento

Per eseguire l'animazione con MotionLayout,, devi convertire ConstraintLayout in MotionLayout.

Affinché il layout possa utilizzare una scena di movimento, deve puntare su di essa.

  1. Per farlo, apri la sezione di progettazione. In Android Studio 4.0, puoi aprire l'area di progettazione utilizzando l'icona di suddivisione o di progettazione in alto a destra quando esamini un file XML di layout.

a2beea710c2decb7.png

  1. Una volta aperta la superficie di progettazione, fai clic con il pulsante destro del mouse sull'anteprima e seleziona Converti in layout Movimento.

4fa936a98a8393b9.png

In questo modo, il tag ConstraintLayout viene sostituito con un tag MotionLayout e viene aggiunto un motion:layoutDescription al tag MotionLayout che rimanda a @xml/activity_step1_scene.

activity_step1**.xml**

<!-- explore motion:layoutDescription="@xml/activity_step1_scene" -->
<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/activity_step1_scene">

Una scena di movimento è un singolo file XML che descrive un'animazione in un MotionLayout.

Non appena esegui la conversione in MotionLayout, nella superficie di progettazione viene visualizzato Editor movimento

66d0e80d5ab4daf8.png

L'editor di movimento contiene tre nuovi elementi UI:

  1. Panoramica: si tratta di una selezione modale che consente di selezionare diverse parti dell'animazione. In questa immagine è selezionato start ConstraintSet. Puoi anche selezionare la transizione tra start e end facendo clic sulla freccia tra i due elementi.
  2. Sezione: sotto la panoramica è presente una finestra di sezione che cambia in base all'elemento della panoramica attualmente selezionato. In questa immagine, le informazioni start per ConstraintSet vengono visualizzate nella finestra di selezione.
  3. Attributo: il riquadro degli attributi mostra e consente di modificare gli attributi dell'elemento attualmente selezionato dalla finestra Panoramica o dalla finestra di selezione. In questa immagine, vengono mostrati gli attributi per start ConstraintSet.

Passaggio 3: definisci i vincoli di inizio e fine

Tutte le animazioni possono essere definite in termini di inizio e fine. All'inizio viene descritto l'aspetto della schermata prima dell'animazione, mentre alla fine viene descritto l'aspetto della schermata al termine dell'animazione. MotionLayout si occupa di capire come eseguire l'animazione tra lo stato iniziale e quello finale (nel tempo).

MotionScene utilizza un tag ConstraintSet per definire gli stati di inizio e di fine. ConstraintSet è come sembra, un insieme di vincoli che possono essere applicati alle visualizzazioni. Sono inclusi i vincoli di larghezza, altezza e ConstraintLayout. Include anche alcuni attributi come alpha. Non contiene le visualizzazioni stesse, ma solo i relativi vincoli.

Tutti i vincoli specificati in ConstraintSet sostituiranno quelli specificati nel file di layout. Se definisci i vincoli sia nel layout sia in MotionScene, vengono applicati solo i vincoli in MotionScene.

In questo passaggio, bloccherai la visualizzazione a stella affinché inizi nella parte superiore dello schermo e termini nella parte inferiore dello schermo.

Puoi completare questo passaggio usando l'editor di movimento o modificando direttamente il testo di activity_step1_scene.xml.

  1. Seleziona start VincoloSet nel riquadro Panoramica

6e57661ed358b860.png

  1. Nel riquadro di selezione, seleziona red_star. Al momento mostra l'origine di layout, il che significa che non è vincolata in questo ConstraintSet. Utilizza l'icona a forma di matita in alto a destra per creare un vincolo

f9564c574b86ea8.gif

  1. Verifica che red_star mostri start quando selezioni ConstraintSet start nel riquadro Panoramica.
  2. Nel riquadro Attributi, con red_star selezionato in ConstraintSet di start, aggiungi un vincolo in alto e inizia facendo clic sui pulsanti + blu.

2fce076cd7b04bd.png

  1. Apri xml/activity_step1_scene.xml per vedere il codice generato da Motion Editor per questo vincolo.

activity_step1_scene.xml

<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>

L'elemento ConstraintSet ha un valore id pari a @id/start e specifica tutti i vincoli da applicare a tutte le viste in MotionLayout. Poiché questo MotionLayout ha una sola visualizzazione, ne richiede una sola Constraint.

L'elemento Constraint all'interno di ConstraintSet specifica l'ID della vista che è vincolante, @id/red_star definito in activity_step1.xml. È importante notare che i tag Constraint specificano solo vincoli e informazioni sul layout. Il tag Constraint non sa che è stato applicato a un ImageView.

Questo vincolo specifica l'altezza, la larghezza e gli altri due vincoli necessari per vincolare la vista red_star all'inizio superiore dell'elemento principale.

  1. Seleziona il Set di vincoli end nel riquadro Panoramica.

346e1248639b6f1e.png

  1. Segui gli stessi passaggi di prima per aggiungere un Constraint per red_star in end ConstraintSet.
  2. Per utilizzare l'editor di movimento per completare questo passaggio, aggiungi un vincolo a bottom e end facendo clic sui pulsanti blu +.

fd33c779ff83c80a.png

  1. Il codice in XML è simile al seguente:

activitiy_step1_scene.xml

<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>

Proprio come @id/start, questo ConstraintSet ha un singolo Constraint su @id/red_star. Questa volta lo vincola all'estremità inferiore dello schermo.

Non è necessario nominare @id/start e @id/end, ma è consigliabile.

Passaggio 4: definisci una transizione

Ogni MotionScene deve includere anche almeno una transizione. Una transizione definisce ogni parte di un'animazione, dall'inizio alla fine.

Una transizione deve specificare un ConstraintSet di inizio e di fine. Una transizione può anche specificare come modificare l'animazione in altri modi, ad esempio per quanto tempo deve essere eseguita l'animazione o come animarla trascinando le visualizzazioni.

  1. Durante la creazione del file MotionScene, Motion Editor ha creato una transizione per noi per impostazione predefinita. Apri activity_step1_scene.xml per vedere la transizione generata.

activity_step1_scene.xml

<!-- A transition describes an animation via start and end state -->
<Transition
   motion:constraintSetEnd="@+id/end"
   motion:constraintSetStart="@id/start"
   motion:duration="1000">
  <KeyFrameSet>
  </KeyFrameSet>
</Transition>

Questo è tutto ciò di cui MotionLayout ha bisogno per creare un'animazione. Osservando ogni attributo:

  • constraintSetStart verrà applicato alle visualizzazioni all'avvio dell'animazione.
  • constraintSetEnd verrà applicato alle visualizzazioni alla fine dell'animazione.
  • duration specifica la durata dell'animazione in millisecondi.

MotionLayout quindi creerà un percorso tra i vincoli di inizio e fine e lo anima per la durata specificata.

Passaggio 5: visualizza l'anteprima dell'animazione in Motion Editor

dff9ecdc1f4a0740.gif

Animazione:video della riproduzione di un'anteprima della transizione in Motion Editor

  1. Apri Motion Editor e seleziona la transizione facendo clic sulla freccia tra start e end nel riquadro Panoramica.

1dc541ae8c43b250.png

  1. Quando è selezionata una transizione, il riquadro di selezione mostra i controlli di riproduzione e una barra di scorrimento. Fai clic su Riproduci o trascina la posizione corrente per visualizzare l'anteprima dell'animazione.

a0fd2593384dfb36.png

Passaggio 6: aggiungi un gestore dei clic

Devi trovare un modo per avviare l'animazione. Un modo per farlo è fare in modo che MotionLayout risponda agli eventi di clic su @id/red_star.

  1. Apri l'editor di movimento e seleziona la transizione facendo clic sulla freccia tra l'inizio e la fine nel riquadro Panoramica.

b6f94b344ce65290.png

  1. Fai clic su 699f7ae04024ccf6.png Crea gestore di clic o scorrimento nella barra degli strumenti del riquadro Panoramica . Viene aggiunto un gestore che avvierà una transizione.
  2. Seleziona Click Identifier dal popup.

ccf92d06335105fe.png

  1. Cambia la View To Click in red_star.

b0d3f0c970604f01.png

  1. Fai clic su Aggiungi: il gestore dei clic è rappresentato da un piccolo puntino nella transizione dell'editor del movimento.

cec3913e67fb4105.png

  1. Con la transizione selezionata nel riquadro Panoramica, aggiungi un attributo clickAction di toggle al gestore OnClick che hai appena aggiunto nel riquadro degli attributi.

9af6fc60673d093d.png

  1. Apri activity_step1_scene.xml per vedere il codice generato da Motion Editor

activity_step1_scene.xml

<!-- A transition describes an animation via start and end state -->
<Transition
    motion:constraintSetStart="@+id/start"
    motion:constraintSetEnd="@+id/end"
    motion:duration="1000">
    <!-- MotionLayout will handle clicks on @id/red_star to "toggle" the animation between the start and end -->
    <OnClick
        motion:targetId="@id/red_star"
        motion:clickAction="toggle" />
</Transition>

Transition indica a MotionLayout di eseguire l'animazione in risposta agli eventi di clic utilizzando un tag <OnClick>. Osservando ogni attributo:

  • targetId è la visualizzazione per cui tenere d'occhio i clic.
  • clickAction di toggle passerà dallo stato iniziale a quello finale al clic. Puoi visualizzare altre opzioni per clickAction nella documentazione.
  1. Esegui il codice, fai clic sul Passaggio 1, poi sulla stella rossa e visualizza l'animazione.

Passaggio 5: le animazioni in azione

Esegui l'app. Dovresti vedere l'animazione quando fai clic sulla stella.

7ba88af963fdfe10.gif

Il file della scena di movimento completato definisce un Transition che punta all'inizio e alla fine di ConstraintSet.

All'inizio dell'animazione (@id/start), l'icona a forma di stella è bloccata nella parte superiore dello schermo. Alla fine dell'animazione (@id/end) l'icona a forma di stella è bloccata nella parte inferiore dello schermo.

<?xml version="1.0" encoding="utf-8"?>

<!-- Describe the animation for activity_step1.xml -->
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:android="http://schemas.android.com/apk/res/android">
   <!-- A transition describes an animation via start and end state -->
   <Transition
           motion:constraintSetStart="@+id/start"
           motion:constraintSetEnd="@+id/end"
           motion:duration="1000">
       <!-- MotionLayout will handle clicks on @id/star to "toggle" the animation between the start and end -->
       <OnClick
               motion:targetId="@id/red_star"
               motion:clickAction="toggle" />
   </Transition>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/start">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               motion:layout_constraintStart_toStartOf="parent"
               motion:layout_constraintTop_toTopOf="parent" />
   </ConstraintSet>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/end">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               motion:layout_constraintEnd_toEndOf="parent"
               motion:layout_constraintBottom_toBottomOf="parent" />
   </ConstraintSet>
</MotionScene>

4. Animazione basata su eventi di trascinamento

Per questo passaggio creerai un'animazione che risponda a un evento di trascinamento dell'utente (quando l'utente fa scorrere lo schermo) per eseguire l'animazione. MotionLayout supporta il monitoraggio degli eventi di tocco per spostare le visualizzazioni, nonché i gesti di scorrimento basati sulla fisica per rendere fluido il movimento.

Passaggio 1: controlla il codice iniziale

  1. Per iniziare, apri il file di layout activity_step2.xml che contiene un file MotionLayout esistente. Dai un'occhiata al codice.

activity_step2.xml

<!-- initial code -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/step2" >

   <ImageView
           android:id="@+id/left_star"
           ...
   />

   <ImageView
           android:id="@+id/right_star"
           ...
   />

   <ImageView
           android:id="@+id/red_star"
           ...
   />

   <TextView
           android:id="@+id/credits"
           ...
           motion:layout_constraintTop_toTopOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.motion.widget.MotionLayout>

Questo layout definisce tutte le visualizzazioni dell'animazione. Le icone con tre stelle non sono vincolate nel layout perché saranno animate nella scena in movimento.

Ai crediti TextView sono applicati vincoli perché rimane nella stessa posizione per l'intera animazione e non modifica alcun attributo.

Passaggio 2: crea l'animazione della scena

Proprio come l'ultima animazione, l'animazione sarà definita da un inizio e una fine ConstraintSet, e da un Transition.

Definisci il valore ConstraintSet iniziale

  1. Apri la scena di movimento xml/step2.xml per definire l'animazione.
  2. Aggiungi i vincoli per il vincolo iniziale start. All'inizio, tutte e tre le stelle sono centrate nella parte inferiore dello schermo. Le stelle a destra e a sinistra hanno un valore alpha pari a 0.0, che indica che sono completamente trasparenti e nascoste.

step2.xml

<!-- TODO apply starting constraints -->

<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />

   <Constraint
           android:id="@+id/left_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="0.0"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />

   <Constraint
           android:id="@+id/right_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="0.0"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>

In questo campo ConstraintSet, specificherai un Constraint per ciascuna delle stelle. Ogni vincolo verrà applicato da MotionLayout all'inizio dell'animazione.

Ogni visualizzazione a stella è centrata nella parte inferiore dello schermo tramite i punti di inizio, fine e basso. Le due stelle @id/left_star e @id/right_star hanno entrambe un valore alpha aggiuntivo che le rende invisibili e che verrà applicato all'inizio dell'animazione.

Gli insiemi di vincoli start e end definiscono l'inizio e la fine dell'animazione. Un vincolo all'inizio, ad esempio motion:layout_constraintStart_toStartOf, impedirà all'inizio di una vista di passare a un'altra. Inizialmente ciò può creare confusione, perché il nome start viene utilizzato sia per sia per entrambi i casi nel contesto dei vincoli. Per fare questa distinzione, start in layout_constraintStart fa riferimento alla parola "inizio" della visualizzazione, ovvero da sinistra in una lingua da sinistra a destra e da destra a sinistra in una lingua. L'insieme di vincoli start fa riferimento all'inizio dell'animazione.

Definire il valore ConstraintSet finale

  1. Definisci il vincolo finale per utilizzare una catena per posizionare tutte e tre le stelle insieme al di sotto di @id/credits. Inoltre, verrà impostato il valore finale delle stelle (alpha) delle stelle sinistra e destra su 1.0.

step2.xml

<!-- TODO apply ending constraints -->

<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">

   <Constraint
           android:id="@+id/left_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="1.0"
           motion:layout_constraintHorizontal_chainStyle="packed"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toStartOf="@id/red_star"
           motion:layout_constraintTop_toBottomOf="@id/credits" />

   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toEndOf="@id/left_star"
           motion:layout_constraintEnd_toStartOf="@id/right_star"
           motion:layout_constraintTop_toBottomOf="@id/credits" />

   <Constraint
           android:id="@+id/right_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="1.0"
           motion:layout_constraintStart_toEndOf="@id/red_star"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintTop_toBottomOf="@id/credits" />
</ConstraintSet>

Il risultato finale è che le visualizzazioni si distenderanno verso l'alto e verso l'alto dal centro mentre si animano.

Inoltre, poiché la proprietà alpha è impostata su @id/right_start e @id/left_star in entrambi i tipi di ConstraintSets, entrambe le viste appariranno dissolventi man mano che l'animazione procede.

Animazione basata sullo scorrimento dell'utente

MotionLayout può monitorare gli eventi di trascinamento dell'utente o un gesto di scorrimento per creare una "fling" basata sulla fisica l'animazione. Ciò significa che le visualizzazioni continueranno se l'utente le lancia e rallenterà come un oggetto fisico quando rotola su una superficie. Puoi aggiungere questo tipo di animazione con un tag OnSwipe in Transition.

  1. Sostituisci TODO per l'aggiunta di un tag OnSwipe con <OnSwipe motion:touchAnchorId="@id/red_star" />.

step2.xml

<!-- TODO add OnSwipe tag -->

<!-- A transition describes an animation via start and end state -->
<Transition
       motion:constraintSetStart="@+id/start"
       motion:constraintSetEnd="@+id/end">
   <!-- MotionLayout will track swipes relative to this view -->
   <OnSwipe motion:touchAnchorId="@id/red_star" />
</Transition>

OnSwipe contiene alcuni attributi, il più importante è touchAnchorId.

  • touchAnchorId è la visualizzazione monitorata che si sposta in risposta al tocco. MotionLayout manterrà la visualizzazione alla stessa distanza dal dito che sta scorrendo.
  • touchAnchorSide determina quale lato della visualizzazione deve essere monitorato. Questo aspetto è importante per le viste che si ridimensionano, seguono percorsi complessi o hanno un lato che si muove più velocemente dell'altro.
  • dragDirection determina la direzione importante per questa animazione (su, giù, sinistra o destra).

Quando MotionLayout rimane in ascolto degli eventi di trascinamento, il listener verrà registrato nella visualizzazione MotionLayout e non nella visualizzazione specificata da touchAnchorId. Quando un utente avvia un gesto in qualsiasi punto dello schermo, MotionLayout manterrà costante la distanza tra il suo dito e il touchAnchorSide della visualizzazione touchAnchorId. Se toccano 100 dp dal lato dell'ancoraggio, ad esempio, MotionLayout manterrà quel lato a 100 dp dal dito per l'intera animazione.

Prova

  1. Esegui di nuovo l'app e apri la schermata del Passaggio 2. Verrà visualizzata l'animazione.
  2. Prova a "ballare" o sollevando il dito a metà dell'animazione per scoprire come MotionLayout mostra animazioni fluide basate sulla fisica.

fefcdd690a0dcaec.gif

MotionLayout può animare design molto diversi utilizzando le funzionalità di ConstraintLayout per creare effetti intensi.

In questa animazione, per iniziare, tutte e tre le visualizzazioni sono posizionate rispetto all'elemento principale nella parte inferiore dello schermo. Alla fine, le tre viste sono posizionate rispetto a @id/credits in una catena.

Nonostante questi layout molto diversi, MotionLayout creerà un'animazione fluida tra l'inizio e la fine.

5. Modifica di un percorso

In questo passaggio creerai un'animazione che segue un percorso complesso durante l'animazione e anima i crediti durante il movimento. MotionLayout può modificare il percorso seguito da una visualizzazione tra l'inizio e la fine utilizzando un KeyPosition.

Passaggio 1: esplora il codice esistente

  1. Apri layout/activity_step3.xml e xml/step3.xml per vedere il layout e la scena di movimento esistenti. ImageView e TextView mostrano la luna e il testo dei crediti.
  2. Apri il file della scena di movimento (xml/step3.xml). Puoi notare che è definito un Transition da @id/start a @id/end. L'animazione sposta l'immagine della luna dalla parte in basso a sinistra dello schermo a quella in basso a destra usando due ConstraintSets. Il testo dei crediti va in dissolvenza da alpha="0.0" a alpha="1.0" mentre la luna si muove.
  3. Esegui l'app ora e seleziona Passaggio 3. Vedrai che la luna segue un percorso lineare (o una linea retta) dall'inizio alla fine quando fai clic sulla luna.

Passaggio 2: attiva il debug del percorso

Prima di aggiungere un arco al movimento della luna, è utile attivare il debug del percorso in MotionLayout.

Per sviluppare animazioni complesse con MotionLayout, puoi tracciare il percorso di animazione di ogni vista. Ciò è utile per visualizzare l'animazione e per regolare con precisione i piccoli dettagli del movimento.

  1. Per attivare i percorsi di debug, apri layout/activity_step3.xml e aggiungi motion:motionDebug="SHOW_PATH" al tag MotionLayout.

activity_step3.xml

<!-- Add motion:motionDebug="SHOW_PATH" -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:motionDebug="SHOW_PATH" >

Dopo aver attivato il debug dei percorsi, quando esegui nuovamente l'app, i percorsi di tutte le visualizzazioni verranno mostrati con una linea tratteggiata.

23bbb604f456f65c.png

  • I cerchi rappresentano la posizione iniziale o finale di una visualizzazione.
  • Le linee rappresentano il percorso di una vista.
  • I diamanti rappresentano un KeyPosition che modifica il percorso.

Ad esempio, in questa animazione, il cerchio centrale è la posizione del testo dei crediti.

Passaggio 3: modifica un percorso

Tutte le animazioni in MotionLayout sono definite da un ConstraintSet di inizio e di fine, che definisce l'aspetto della schermata prima dell'inizio e dopo il completamento dell'animazione. Per impostazione predefinita, MotionLayout traccia un percorso lineare (una linea retta) tra la posizione iniziale e quella finale di ogni vista che cambia posizione.

Per creare percorsi complessi come l'arco lunare in questo esempio, MotionLayout utilizza un KeyPosition per modificare il percorso di una vista tra l'inizio e la fine.

  1. Apri xml/step3.xml e aggiungi KeyPosition alla scena. Il tag KeyPosition è inserito all'interno del tag Transition.

eae4dae9a12d0410.png

step3.xml

<!-- TODO: Add KeyFrameSet and KeyPosition -->
<KeyFrameSet>
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
   />
</KeyFrameSet>

KeyFrameSet è un elemento secondario di Transition ed è un insieme di tutti gli elementi KeyFrames, ad esempio KeyPosition, da applicare durante la transizione.

Poiché MotionLayout sta calcolando il percorso per la luna tra l'inizio e la fine, modifica il percorso in base al valore KeyPosition specificato nelle KeyFrameSet. Esegui di nuovo l'app per vedere come questo modifica il percorso.

Un elemento KeyPosition ha diversi attributi che descrivono il modo in cui modifica il percorso. I più importanti sono:

  • framePosition è un numero compreso tra 0 e 100. Definisce quando deve essere applicato questo KeyPosition nell'animazione; 1 corrisponde all'1% dell'animazione e 99 al 99% dell'animazione. Quindi, se il valore è 50, lo applichi direttamente al centro.
  • motionTarget è la vista per cui questo KeyPosition modifica il percorso.
  • keyPositionType è il modo in cui questo KeyPosition modifica il percorso. Può essere parentRelative, pathRelative o deltaRelative (come spiegato nel passaggio successivo).
  • percentX | percentY è l'importo da modificare per il percorso in framePosition (valori compresi tra 0,0 e 1,0, con valori negativi e valori >1 consentiti).

Si pensa così: "All'interno di framePosition modifica il percorso di motionTarget spostandolo di percentX o di percentY in base alle coordinate stabilite da keyPositionType."

Per impostazione predefinita, MotionLayout arrotonda tutti gli angoli introdotti modificando il percorso. Se osservi l'animazione che hai appena creato, puoi vedere che la luna segue un percorso curvo in corrispondenza della curva. Questo è ciò che vuoi ottenere per la maggior parte delle animazioni. In caso contrario, puoi specificare l'attributo curveFit per personalizzarlo.

Prova

Se riavvii l'app, viene visualizzata l'animazione di questo passaggio.

46b179c01801f19e.gif

La luna segue un arco perché passa attraverso un valore KeyPosition specificato nell'elemento Transition.

<KeyPosition
       motion:framePosition="50"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.5"
/>

Puoi leggere questo KeyPosition come: "In framePosition 50 (a metà dell'animazione) modifica il percorso di motionTarget @id/moon spostandolo di 50% Y (a metà dello schermo) in base alle coordinate determinate da parentRelative (l'intero MotionLayout)."

Quindi, a metà dell'animazione, la luna deve passare attraverso un KeyPosition posizionato al 50% in basso sullo schermo. Questo KeyPosition non modifica affatto il movimento X, quindi la luna passerà comunque dall'inizio alla fine in orizzontale. MotionLayout individuerà un percorso fluido che attraversa questo KeyPosition passando dall'inizio alla fine e viceversa.

Se guardi attentamente, il testo dei crediti è vincolato dalla posizione della luna. Perché non si muove anche in verticale?

1c7cf779931e45cc.gif

<Constraint
       android:id="@id/credits"
       ...
       motion:layout_constraintBottom_toBottomOf="@id/moon"
       motion:layout_constraintTop_toTopOf="@id/moon"
/>

A quanto pare, anche se si sta modificando il percorso della luna, le posizioni di inizio e fine della luna non la spostano affatto in verticale. KeyPosition non modifica la posizione iniziale o finale, quindi il testo dei crediti è vincolato alla posizione finale finale della luna.

Se vuoi che i crediti si spostino con la luna, puoi aggiungere KeyPosition ai crediti o modificare i vincoli di inizio su @id/credits.

Nella sezione successiva, analizzerai in dettaglio i diversi tipi di keyPositionType in MotionLayout.

6. Informazioni su keyPositionType

Nell'ultimo passaggio hai utilizzato un tipo keyPosition di parentRelative per eseguire l'offset del percorso del 50% dello schermo. L'attributo keyPositionType determina il modo in cui MotionLayout modificherà il percorso secondo percentX o percentY.

<KeyFrameSet>
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
   />
</KeyFrameSet>

Sono possibili tre diversi tipi di keyPosition: parentRelative, pathRelative e deltaRelative. Se specifichi un tipo, verrà modificato il sistema di coordinate con cui vengono calcolati percentX e percentY.

Che cos'è un sistema di coordinate?

Un sistema di coordinate consente di specificare un punto nello spazio. Sono utili anche per descrivere una posizione sullo schermo.

I sistemi di coordinate di MotionLayout sono un sistema di coordinate cartesiano. ovvero gli assi X e Y sono definiti da due rette perpendicolari. La differenza principale è il punto sullo schermo in cui si inserisce l'asse X (l'asse Y è sempre perpendicolare all'asse X).

Tutti i sistemi di coordinate in MotionLayout utilizzano valori compresi tra 0.0 e 1.0 su entrambi gli assi X e Y. Consentono valori negativi e valori superiori a 1.0. Ad esempio, un valore percentX di -2.0 significa che devi andare nella direzione opposta dell'asse X due volte.

Se ti sembra un po' troppo legato alla lezione di algebra, dai un'occhiata alle immagini qui sotto.

Coordinate padreParent

a7b7568d46d9dec7.png

keyPositionType di parentRelative utilizza lo stesso sistema di coordinate dello schermo. Definisce (0, 0) in alto a sinistra dell'intero MotionLayout e (1, 1) in basso a destra.

Puoi utilizzare parentRelative ogni volta che vuoi creare un'animazione che si sposta sull'intero MotionLayout, come l'arco lunare in questo esempio.

Tuttavia, se vuoi modificare un percorso relativo al movimento, ad esempio curvarlo leggermente, gli altri due sistemi di coordinate sono la scelta migliore.

Coordinate deltarelative

5680bf553627416c.png

Delta è un termine matematico che indica la variazione, quindi deltaRelative è un modo per dire "cambia relativo". Nelle deltaRelative coordinate(0,0) è la posizione iniziale della vista, mentre (1,1) è la posizione finale. Gli assi X e Y sono allineati allo schermo.

L'asse X sullo schermo è sempre orizzontale e l'asse Y è sempre verticale sullo schermo. Rispetto a parentRelative, la differenza principale è che le coordinate descrivono solo la parte dello schermo su cui si sposterà la visualizzazione.

deltaRelative è un ottimo sistema di coordinate per controllare il movimento orizzontale o verticale in modo isolato. Ad esempio, puoi creare un'animazione che completa solo il suo movimento verticale (Y) al 50% e continua ad animarsi orizzontalmente (X).

Coordinate relative

f3aaadaac8b4a93f.png

L'ultimo sistema di coordinate in MotionLayout è pathRelative. È molto diverso dagli altri due, in quanto l'asse X segue il percorso di animazione dall'inizio alla fine. Quindi (0,0) è la posizione iniziale e (1,0) è la posizione finale.

Perché vuoi riceverlo? È abbastanza sorprendente a prima vista, soprattutto perché questo sistema di coordinate non è nemmeno allineato a quello sullo schermo.

A quanto pare, pathRelative è davvero utile per alcune cose.

  • Accelerare, rallentare o interrompere la visualizzazione durante una parte dell'animazione. Poiché la dimensione X corrisponderà sempre esattamente al percorso seguito dalla visualizzazione, puoi utilizzare un KeyPosition pathRelative per modificare la modalità framePosition raggiunta da un determinato punto del percorso. Di conseguenza, un KeyPosition in framePosition="50" con un percentX="0.1" fa sì che l'animazione impieghi il 50% del tempo per percorrere il primo 10% del movimento.
  • Aggiunta di un sottile arco a un percorso. Poiché la dimensione Y è sempre perpendicolare al movimento, la modifica di Y cambia il percorso in curva rispetto al movimento complessivo.
  • Aggiungi una seconda dimensione quando deltaRelative non funziona. Per il movimento completamente orizzontale e verticale, deltaRelative creerà solo una dimensione utile. Tuttavia, pathRelative creerà sempre coordinate X e Y utilizzabili.

Nel passaggio successivo imparerai a creare percorsi ancora più complessi utilizzando più di un KeyPosition.

7. Costruire percorsi complessi

Osservando l'animazione che hai creato nell'ultimo passaggio, si crea una curva fluida, ma la forma potrebbe essere più simile alla luna.

Modificare un percorso con più elementi KeyPosition

MotionLayout può modificare ulteriormente un percorso definendo il numero di KeyPosition necessario per ottenere qualsiasi movimento. Per questa animazione creerai un arco, ma se vuoi potresti far saltare la luna su e giù al centro dello schermo.

  1. Apri xml/step4.xml. Come vedi, ha le stesse visualizzazioni e lo KeyFrame che hai aggiunto nell'ultimo passaggio.
  2. Per arrotondare la parte superiore della curva, aggiungi altri due KeyPositions al percorso di @id/moon, uno appena prima che raggiunga la cima e uno dopo.

500b5ac2db48ef87.png

step4.xml

<!-- TODO: Add two more KeyPositions to the KeyFrameSet here -->
<KeyPosition
       motion:framePosition="25"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.6"
/>
<KeyPosition
       motion:framePosition="75"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.6"
/>

Questi KeyPositions verranno applicati al 25% e al 75% dell'animazione e faranno sì che @id/moon si muova lungo un percorso che si trova al 60% dalla parte superiore dello schermo. Combinato con il valore attuale di KeyPosition al 50%, viene creato un arco uniforme che la luna farà seguire.

In MotionLayout, puoi aggiungere tutti i KeyPositions necessari per ottenere il percorso di animazione che preferisci. MotionLayout applicherà ogni KeyPosition al framePosition specificato e capirà come creare un movimento fluido che attraversa tutti i KeyPositions.

Prova

  1. Esegui di nuovo l'app. Vai al passaggio 4 per vedere come funziona l'animazione. Quando fai clic sulla luna, questo segue il percorso dall'inizio alla fine, attraversando ogni KeyPosition specificato nell'KeyFrameSet.

Esplora in autonomia

Prima di passare ad altri tipi di KeyFrame, prova ad aggiungere altri KeyPositions a KeyFrameSet per vedere che tipo di effetti puoi creare usando solo KeyPosition.

Questo è un esempio che mostra come creare un percorso complesso che si sposta avanti e indietro durante l'animazione.

cd9faaffde3dfef.png

step4.xml

<!-- Complex paths example: Dancing moon -->
<KeyFrameSet>
   <KeyPosition
           motion:framePosition="25"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.6"
           motion:percentX="0.1"
   />
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
           motion:percentX="0.3"
   />
   <KeyPosition
           motion:framePosition="75"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.6"
           motion:percentX="0.1"
   />
</KeyFrameSet>

Dopo aver esplorato KeyPosition, nel passaggio successivo passerai ad altri tipi di KeyFrames.

8. Modificare gli attributi durante il movimento

La creazione di animazioni dinamiche spesso comporta la modifica di size, rotation o alpha delle visualizzazioni man mano che l'animazione progredisce. MotionLayout supporta l'animazione di molti attributi in qualsiasi vista utilizzando un KeyAttribute.

In questo passaggio, utilizzerai KeyAttribute per scalare e ruotare la luna. Utilizzerai anche un KeyAttribute per ritardare la comparsa del testo finché la luna non avrà quasi completato il suo viaggio.

Passaggio 1: ridimensiona e ruota con KeyAttribute

  1. Apri xml/step5.xml, che contiene la stessa animazione che hai creato nell'ultimo passaggio. Per maggiore varietà, questa schermata utilizza un'immagine dello spazio diversa come sfondo.
  2. Per far sì che la luna si espanda di dimensioni e ruoti, aggiungi due tag KeyAttribute in KeyFrameSet in keyFrame="50" e keyFrame="100"

bbae524a2898569.png

step5.xml

<!-- TODO: Add KeyAttributes to rotate and resize @id/moon -->

<KeyAttribute
       motion:framePosition="50"
       motion:motionTarget="@id/moon"
       android:scaleY="2.0"
       android:scaleX="2.0"
       android:rotation="-360"
/>
<KeyAttribute
       motion:framePosition="100"
       motion:motionTarget="@id/moon"
       android:rotation="-720"
/>

Questi KeyAttributes vengono applicati al 50% e al 100% dell'animazione. Il primo KeyAttribute al 50% avverrà nella parte superiore dell'arco e determina il raddoppiamento delle dimensioni della vista e la rotazione di -360 gradi (o un cerchio intero). Il secondo KeyAttribute terminerà la seconda rotazione a -720 gradi (due cerchi completi) e ridurrà le dimensioni al valore normale poiché i valori di scaleX e scaleY saranno impostati su 1,0 per impostazione predefinita.

Proprio come un KeyPosition, un KeyAttribute utilizza i framePosition e motionTarget per specificare quando applicare l'KeyFrame e la vista da modificare. MotionLayout eseguirà l'interpolazione tra KeyPositions per creare animazioni fluide.

KeyAttributes supporta gli attributi che possono essere applicati a tutte le viste. Supportano la modifica degli attributi di base, ad esempio visibility, alpha o elevation. Puoi anche modificare la rotazione come stai facendo qui, ruotare tre dimensioni con rotateX e rotateY, ridimensionare le dimensioni con scaleX e scaleY o tradurre la posizione della vista in X, Y o Z.

Passaggio 2: posticipa la visualizzazione dei riconoscimenti

Uno degli obiettivi di questo passaggio è aggiornare l'animazione in modo che il testo dei riconoscimenti non venga visualizzato fino al completamento dell'animazione.

  1. Per ritardare la visualizzazione dei crediti, definisci un altro KeyAttribute in modo che alpha rimanga pari a 0 fino al giorno keyPosition="85". MotionLayout eseguirà comunque la transizione da 0 a 100 alpha senza problemi, ma lo farà nell'ultimo 15% dell'animazione.

step5.xml

<!-- TODO: Add KeyAttribute to delay the appearance of @id/credits -->

<KeyAttribute
       motion:framePosition="85"
       motion:motionTarget="@id/credits"
       android:alpha="0.0"
/>

Questo KeyAttribute mantiene il valore di alpha di @id/credits a 0,0 per il primo 85% dell'animazione. Dal momento che inizia con un alpha pari a 0, significa che sarà invisibile per il primo 85% dell'animazione.

L'effetto finale di questo KeyAttribute è che i riconoscimenti vengono visualizzati verso la fine dell'animazione. In questo modo sembreranno che siano coordinati con la luna che si assesta nell'angolo destro dello schermo.

Posticipando le animazioni in una vista mentre un'altra si muove in questo modo, puoi creare animazioni straordinarie e dinamiche per l'utente.

Prova

  1. Esegui di nuovo l'app e vai al Passaggio 5 per vedere l'animazione in azione. Quando fai clic sulla luna, seguirà il percorso dall'inizio alla fine, passando per ogni KeyAttribute specificato nell'KeyFrameSet.

2f4bfdd681c1fa98.gif

Poiché ruoti la luna di due cerchi pieni, ora eseguirà un doppio capovolgimento e i riconoscimenti ritarderanno la loro visualizzazione fino al termine dell'animazione.

Esplora in autonomia

Prima di passare al tipo finale di KeyFrame, prova a modificare altri attributi standard in KeyAttributes. Ad esempio, prova a modificare rotation in rotationX per vedere quale animazione produce.

Di seguito è riportato un elenco degli attributi standard che puoi provare a utilizzare:

  • android:visibility
  • android:alpha
  • android:elevation
  • android:rotation
  • android:rotationX
  • android:rotationY
  • android:scaleX
  • android:scaleY
  • android:translationX
  • android:translationY
  • android:translationZ

9. Modifica degli attributi personalizzati

Le animazioni avanzate implicano la modifica del colore o di altri attributi di una visualizzazione. Mentre MotionLayout può utilizzare un KeyAttribute per modificare uno qualsiasi degli attributi standard elencati nell'attività precedente, tu utilizzi un CustomAttribute per specificare qualsiasi altro attributo.

È possibile utilizzare un CustomAttribute per impostare qualsiasi valore che abbia un setter. Ad esempio, puoi impostare backgroundColor su una vista utilizzando un CustomAttribute. MotionLayout utilizzerà la riflessione per trovare il setter, quindi lo chiamerà ripetutamente per animare la visualizzazione.

In questo passaggio, utilizzerai un CustomAttribute per impostare l'attributo colorFilter sulla luna e creare l'animazione mostrata di seguito.

5fb6792126a09fda.gif

Definire gli attributi personalizzati

  1. Per iniziare, apri xml/step6.xml, che contiene la stessa animazione che hai creato nell'ultimo passaggio.
  2. Per fare in modo che il colore della luna cambi, aggiungi due KeyAttribute con CustomAttribute in KeyFrameSet alle ore keyFrame="0", keyFrame="50" e keyFrame="100".

214699d5fdd956da.png

step6.xml

<!-- TODO: Add Custom attributes here -->
<KeyAttribute
       motion:framePosition="0"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFFFFF"
   />
</KeyAttribute>
<KeyAttribute
       motion:framePosition="50"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFB612"
   />
</KeyAttribute>
<KeyAttribute
       motion:framePosition="100"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFFFFF"
   />
</KeyAttribute>

Aggiungi CustomAttribute all'interno di un KeyAttribute. Il CustomAttribute verrà applicato al framePosition specificato da KeyAttribute.

All'interno di CustomAttribute, devi specificare un valore attributeName e un valore da impostare.

  • motion:attributeName è il nome del setter che verrà chiamato da questo attributo personalizzato. In questo esempio verrà chiamato setColorFilter su Drawable.
  • motion:custom*Value è un valore personalizzato del tipo indicato nel nome, in questo esempio il valore personalizzato è un colore specificato.

I valori personalizzati possono avere i seguenti tipi:

  • Colore
  • Numero intero
  • Float
  • Stringa
  • Dimensione
  • Booleano

Utilizzando questa API, MotionLayout può animare tutto ciò che fornisce un setter su qualsiasi vista.

Prova

  1. Esegui di nuovo l'app e vai al passaggio 6 per vedere l'animazione in azione. Quando fai clic sulla luna, seguirà il percorso dall'inizio alla fine, passando per ogni KeyAttribute specificato nell'KeyFrameSet.

5fb6792126a09fda.gif

Quando aggiungi altri KeyFrames, MotionLayout cambia il percorso della luna da una linea retta a una curva complessa, aggiungendo un doppio capovolgimento, ridimensionamento e un cambio di colore a metà dell'animazione.

Nelle animazioni reali, spesso puoi animare diverse visualizzazioni contemporaneamente, controllandone il movimento lungo percorsi e velocità diversi. Specificando un KeyFrame diverso per ogni visualizzazione, è possibile creare animazioni avanzate che animano più visualizzazioni con MotionLayout.

10. Eventi di trascinamento e percorsi complessi

In questo passaggio esplorerai l'utilizzo di OnSwipe con percorsi complessi. Finora l'animazione della luna è stata attivata da un listener OnClick e viene eseguita per una durata fissa.

Per controllare le animazioni con percorsi complessi con OnSwipe, come l'animazione della luna che hai creato negli ultimi passaggi, devi comprendere come funziona OnSwipe.

Passaggio 1: esplora il comportamento OnScorrimento

  1. Apri xml/step7.xml e trova la dichiarazione OnSwipe esistente.

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
/>
  1. Esegui l'app sul tuo dispositivo e vai al Passaggio 7. Verifica se riesci a produrre un'animazione fluida trascinando la luna lungo il percorso dell'arco.

Quando esegui questa animazione, il risultato non sarà molto buono. Quando la luna raggiunge la cima dell'arco, inizia a saltellare.

ed96e3674854a548.gif

Per capire il bug, pensa a cosa succede quando l'utente tocca appena sotto la parte superiore dell'arco. Poiché il tag OnSwipe ha un MotionLayout di motion:touchAnchorSide="bottom", cercherà di rendere costante la distanza tra il dito e la parte inferiore della visualizzazione per tutta l'animazione.

Ma, poiché la parte inferiore della luna non sempre va nella stessa direzione, sale e poi di nuovo giù, MotionLayout non sa cosa fare quando l'utente ha appena superato la parte superiore dell'arco. Dato che stai monitorando la parte inferiore della luna, dove dovrebbe essere posizionato quando l'utente tocca questo punto?

56cd575c5c77eddd.png

Passaggio 2: usa il lato destro

Per evitare bug come questo, è importante scegliere sempre touchAnchorId e touchAnchorSide che avanzano sempre in una direzione per tutta la durata dell'intera animazione.

In questa animazione, sia il lato right sia il lato left della luna avanzeranno sullo schermo in una direzione.

Tuttavia, sia bottom che top andranno a invertire la direzione. Quando OnSwipe tenta di monitorarli, si crea confusione se la direzione cambia.

  1. Per fare in modo che l'animazione segua gli eventi touch, modifica touchAnchorSide in right.

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="right"
/>

Passaggio 3: utilizza la direzione di trascinamento

Puoi anche combinare dragDirection con touchAnchorSide per creare una direzione laterale diversa rispetto a quella che faresti normalmente. È comunque importante che touchAnchorSide avanza in una sola direzione, ma puoi indicare a MotionLayout la direzione da monitorare. Ad esempio, puoi mantenere touchAnchorSide="bottom", ma aggiungere dragDirection="dragRight". Questo fa sì che MotionLayout monitori la posizione della parte inferiore della vista, ma ne considererà la posizione solo quando si sposta verso destra (ignora il movimento verticale). In questo modo, anche se le parti in basso e in alto si animano comunque correttamente con OnSwipe.

  1. Aggiorna OnSwipe per monitorare correttamente il movimento della luna.

step7.xml

<!-- Using dragDirection to control the direction of drag tracking 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
       motion:dragDirection="dragRight"
/>

Prova

  1. Esegui di nuovo l'app e prova a trascinare la luna lungo l'intero percorso. Anche se segue un arco complesso, MotionLayout sarà in grado di far avanzare l'animazione in risposta agli eventi di scorrimento.

5458dff382261427.gif

11. Esecuzione di movimenti con codice

L'elemento MotionLayout può essere utilizzato per creare animazioni avanzate con CoordinatorLayout. In questo passaggio, creerai un'intestazione comprimibile utilizzando MotionLayout.

Passaggio 1: esplora il codice esistente

  1. Per iniziare, apri layout/activity_step8.xml.
  2. In layout/activity_step8.xml, puoi vedere che sono già stati creati CoordinatorLayout e AppBarLayout funzionanti.

activity_step8.xml

<androidx.coordinatorlayout.widget.CoordinatorLayout
       ...>
   <com.google.android.material.appbar.AppBarLayout
           android:id="@+id/appbar_layout"
           android:layout_width="match_parent"
           android:layout_height="180dp">
       <androidx.constraintlayout.motion.widget.MotionLayout
               android:id="@+id/motion_layout"
               ... >
           ...
       </androidx.constraintlayout.motion.widget.MotionLayout>
   </com.google.android.material.appbar.AppBarLayout>
  
   <androidx.core.widget.NestedScrollView
           ...
           motion:layout_behavior="@string/appbar_scrolling_view_behavior" >
           ...
   </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

Questo layout utilizza un elemento CoordinatorLayout per condividere le informazioni di scorrimento tra NestedScrollView e AppBarLayout. Pertanto, quando NestedScrollView scorre verso l'alto, indicherà a AppBarLayout la modifica. In questo modo implementi una barra degli strumenti comprimibile come questa su Android: lo scorrimento del testo sarà "coordinato" con l'intestazione compressa.

La scena in movimento a cui punta @id/motion_layout è simile alla scena in movimento nell'ultimo passaggio. Tuttavia, la dichiarazione OnSwipe è stata rimossa per consentirne il funzionamento con CoordinatorLayout.

  1. Esegui l'app e vai al passaggio 8. Quando scorri il testo, la luna non si muove.

Passaggio 2: fai scorrere il layout Movimento

  1. Per fare scorrere la visualizzazione MotionLayout non appena l'NestedScrollView scorre, aggiungi motion:minHeight e motion:layout_scrollFlags alla MotionLayout.

activity_step8.xml

<!-- Add minHeight and layout_scrollFlags to the MotionLayout -->

<androidx.constraintlayout.motion.widget.MotionLayout
       android:id="@+id/motion_layout"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       motion:layoutDescription="@xml/step8"
       motion:motionDebug="SHOW_PATH"
       android:minHeight="80dp"
       motion:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed"  >
  1. Esegui di nuovo l'app e vai al passaggio 8. Come vedi, la barra MotionLayout si comprime mentre scorri verso l'alto. Tuttavia, l'animazione non avanza ancora in base al comportamento di scorrimento.

Passaggio 3: sposta il movimento con il codice

  1. Apri Step8Activity.kt . Modifica la funzione coordinateMotion() per indicare a MotionLayout i cambiamenti nella posizione di scorrimento.

Step8Activity.kt

// TODO: set progress of MotionLayout based on an AppBarLayout.OnOffsetChangedListener

private fun coordinateMotion() {
    val appBarLayout: AppBarLayout = findViewById(R.id.appbar_layout)
    val motionLayout: MotionLayout = findViewById(R.id.motion_layout)

    val listener = AppBarLayout.OnOffsetChangedListener { unused, verticalOffset ->
        val seekPosition = -verticalOffset / appBarLayout.totalScrollRange.toFloat()
        motionLayout.progress = seekPosition
    }

    appBarLayout.addOnOffsetChangedListener(listener)
}

Questo codice registrerà un OnOffsetChangedListener che verrà chiamato ogni volta che l'utente scorrerà con l'offset di scorrimento corrente.

MotionLayout supporta la ricerca della transizione impostando la proprietà di avanzamento. Per convertire da verticalOffset a una percentuale di avanzamento, dividi il risultato per l'intervallo di scorrimento totale.

Prova

  1. Esegui di nuovo il deployment dell'app ed esegui l'animazione Passaggio 8. Puoi notare che MotionLayout farà avanzare l'animazione in base alla posizione di scorrimento.

ee5ce4d9e33a59ca.gif

È possibile creare animazioni dinamiche di compressione della barra degli strumenti personalizzate utilizzando MotionLayout. Utilizzando una sequenza di KeyFrames puoi ottenere effetti molto audaci.

12. Complimenti

Questo codelab ha riguardato l'API di base di MotionLayout.

Per vedere altri esempi pratici di MotionLayout, guarda l'esempio ufficiale. Non perderti la documentazione.

Scopri di più

MotionLayout supporta ancora più funzionalità non trattate in questo codelab, come KeyCycle,, che consente di controllare i percorsi o gli attributi con cicli ricorrenti, e KeyTimeCycle,, che consente di animare in base all'orologio. Dai un'occhiata agli esempi per ciascun suggerimento.

Per i link ad altri codelab di questo corso, consulta la pagina di destinazione avanzata dei codelab Android in Kotlin.