Développement Android avancé en Kotlin – 3.2 : Animation avec MotionLayout

1. Avant de commencer

Cet atelier de programmation fait partie du cours Développement Android avancé en Kotlin. Vous tirerez pleinement parti de ce cours en suivant les ateliers de programmation dans l'ordre, mais ce n'est pas obligatoire. Tous les ateliers de programmation du cours sont listés sur la page de destination des ateliers de programmation Android avancé en Kotlin.

MotionLayout est une bibliothèque qui vous permet d'ajouter des animations riches à votre application Android. Elle est basée sur ConstraintLayout, et vous permet d'animer tout ce que vous pouvez créer à l'aide de ConstraintLayout.

Vous pouvez utiliser MotionLayout pour animer simultanément différents attributs pour plusieurs vues, parmi lesquels la position, la taille, la visibilité, la valeur alpha, la couleur, l'élévation et la rotation. À l'aide de fichiers XML déclaratifs, vous pouvez créer des animations coordonnées impliquant plusieurs vues qui sont difficiles à obtenir par codage.

Les animations sont un excellent moyen d'améliorer l'expérience d'une application. Vous pouvez utiliser des animations pour :

  • Afficher les modifications : l'animation entre les états permet à l'utilisateur de suivre naturellement les modifications apportées à votre UI.
  • Attirer l'attention : utilisez des animations pour attirer l'attention sur les éléments importants de l'UI.
  • Créez des designs attrayants : des mouvements efficaces dans le design donnent aux applications une apparence soignée.

Prérequis

Cet atelier de programmation s'adresse aux développeurs ayant une certaine expérience du développement Android. Avant de commencer cet atelier de programmation, vous devez :

  • Vous savez comment créer une application avec une activité et une mise en page de base, et l'exécuter sur un appareil ou un émulateur à l'aide d'Android Studio. Familiarisez-vous avec ConstraintLayout. Pour en savoir plus sur ConstraintLayout, consultez l'atelier de programmation sur ConstraintLayout.

Objectifs de l'atelier

  • Définir une animation avec ConstraintSets et MotionLayout
  • Animer en fonction des événements de déplacement
  • Modifier l'animation avec KeyPosition
  • Modifier les attributs avec KeyAttribute
  • Exécuter des animations avec du code
  • Animer les en-têtes réductibles avec MotionLayout

Prérequis

  • Android Studio 4.0 (l'éditeur MotionLayout ne fonctionne qu'avec cette version d'Android Studio)

2. Premiers pas

Pour télécharger l'application exemple, vous pouvez :

Vous pouvez également cloner le dépôt GitHub à partir de la ligne de commande à l'aide de la commande suivante :

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

3. Créer des animations avec MotionLayout

Vous allez d'abord créer une animation qui déplace une vue du haut de l'écran vers le bas en réponse aux clics de l'utilisateur.

Pour créer une animation à partir du code de démarrage, vous aurez besoin des éléments principaux suivants :

  • Un MotionLayout, qui est une sous-classe de ConstraintLayout. Vous spécifiez toutes les vues à animer dans la balise MotionLayout.
  • Un MotionScene,, qui est un fichier XML décrivant une animation pour MotionLayout.
  • Un Transition, qui fait partie du MotionScene et qui spécifie la durée de l'animation, le déclencheur et la façon de déplacer les vues.
  • ConstraintSet qui spécifie les contraintes start et end de la transition.

Examinons chacun d'eux, en commençant par MotionLayout.

Étape 1 : Explorer le code existant

MotionLayout est une sous-classe de ConstraintLayout. Elle est donc compatible avec toutes les mêmes fonctionnalités tout en ajoutant une animation. Pour utiliser MotionLayout, vous devez ajouter une vue MotionLayout à l'endroit où vous utiliseriez ConstraintLayout..

  1. Dans res/layout, ouvrez activity_step1.xml.. Vous y trouverez un ConstraintLayout avec un seul ImageView d'étoile, avec une teinte appliquée à l'intérieur.

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>

Cette ConstraintLayout n'a aucune contrainte. Par conséquent, si vous exécutez l'application maintenant, vous verrez l'étoile s'afficher sans contrainte, ce qui signifie qu'elle sera positionnée à un emplacement inconnu. Android Studio vous avertit de l'absence de contraintes.

Étape 2 : Convertissez en Motion Layout

Pour animer à l'aide de MotionLayout,, vous devez convertir le ConstraintLayout en MotionLayout.

Pour que votre mise en page utilise une scène de mouvement, elle doit y faire référence.

  1. Pour ce faire, ouvrez la surface de conception. Dans Android Studio 4.0, vous ouvrez la surface de conception en utilisant l'icône de division ou de conception en haut à droite lorsque vous consultez un fichier XML de mise en page.

a2beea710c2decb7.png

  1. Une fois la surface de conception ouverte, effectuez un clic droit sur l'aperçu et sélectionnez Convert to MotionLayout (Convertir en MotionLayout).

4fa936a98a8393b9.png

Cela remplace la balise ConstraintLayout par une balise MotionLayout et ajoute un motion:layoutDescription à la balise MotionLayout qui pointe vers @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">

Une scène de mouvement est un fichier XML unique qui décrit une animation dans un MotionLayout.

Dès que vous convertissez un MotionLayout, l'Éditeur de mouvement s'affiche sur la surface de conception.

66d0e80d5ab4daf8.png

L'Éditeur de mouvement comporte trois nouveaux éléments d'UI :

  1. Aperçu : il s'agit d'une sélection modale qui vous permet de choisir différentes parties de l'animation. Dans cette image, le ConstraintSet start est sélectionné. Vous pouvez également sélectionner la transition entre start et end en cliquant sur la flèche entre les deux.
  2. Section : sous l'aperçu se trouve une fenêtre de section qui change en fonction de l'élément d'aperçu actuellement sélectionné. Dans cette image, les informations start ConstraintSet s'affichent dans la fenêtre de sélection.
  3. Attribut : le panneau d'attributs affiche et vous permet de modifier les attributs de l'élément actuellement sélectionné dans la fenêtre "Vue d'ensemble" ou "Sélection". Cette image montre les attributs de l'ConstraintSet start.

Étape 3 : Définissez les contraintes de début et de fin

Toutes les animations peuvent être définies en termes de début et de fin. Le début décrit l'apparence de l'écran avant l'animation, et la fin décrit l'apparence de l'écran une fois l'animation terminée. MotionLayout est responsable de la détermination de la manière d'animer entre l'état de début et l'état de fin (au fil du temps).

MotionScene utilise une balise ConstraintSet pour définir les états de début et de fin. Un ConstraintSet est un ensemble de contraintes qui peuvent être appliquées aux vues. Cela inclut les contraintes de largeur, de hauteur et ConstraintLayout. Il inclut également certains attributs tels que alpha. Il ne contient pas les vues elles-mêmes, mais uniquement les contraintes qui s'y appliquent.

Toutes les contraintes spécifiées dans un fichier ConstraintSet remplaceront celles spécifiées dans le fichier de mise en page. Si vous définissez des contraintes à la fois dans la mise en page et dans MotionScene, seules les contraintes de MotionScene sont appliquées.

Dans cette étape, vous allez contraindre la vue en étoile à commencer en haut à gauche de l'écran et à se terminer en bas à droite.

Vous pouvez effectuer cette étape à l'aide de l'éditeur de mouvement ou en modifiant directement le texte de activity_step1_scene.xml.

  1. Sélectionnez le start ConstraintSet dans le panneau "Vue d'ensemble".

6e57661ed358b860.png

  1. Dans le panneau Sélection, sélectionnez red_star. Actuellement, la source layout s'affiche, ce qui signifie qu'elle n'est pas contrainte dans ce ConstraintSet. Utilisez l'icône en forme de crayon en haut à droite pour créer une contrainte.

f9564c574b86ea8.gif

  1. Vérifiez que red_star affiche une source start lorsque l'start ConstraintSet est sélectionné dans le panneau "Vue d'ensemble".
  2. Dans le panneau "Attributes" (Attributs), avec red_star sélectionné dans start ConstraintSet, ajoutez une contrainte en haut et commencez par cliquer sur les boutons bleus +.

2fce076cd7b04bd.png

  1. Ouvrez xml/activity_step1_scene.xml pour afficher le code généré par l'éditeur de mouvements pour cette contrainte.

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>

ConstraintSet a une id de @id/start et spécifie toutes les contraintes à appliquer à toutes les vues de MotionLayout. Étant donné que ce MotionLayout ne comporte qu'une seule vue, il n'a besoin que d'un seul Constraint.

Le Constraint à l'intérieur de ConstraintSet spécifie l'ID de la vue qu'il contraint, @id/red_star défini dans activity_step1.xml. Il est important de noter que les balises Constraint ne spécifient que les contraintes et les informations de mise en page. La balise Constraint ne sait pas qu'elle est appliquée à un ImageView.

Cette contrainte spécifie la hauteur, la largeur et les deux autres contraintes nécessaires pour contraindre la vue red_star au début supérieur de son parent.

  1. Sélectionnez le end ConstraintSet dans le panneau "Vue d'ensemble".

346e1248639b6f1e.png

  1. Suivez les mêmes étapes que précédemment pour ajouter un Constraint pour red_star dans le ConstraintSet end.
  2. Pour utiliser l'éditeur de mouvement pour effectuer cette étape, ajoutez une contrainte aux bottom et end en cliquant sur les boutons bleus +.

fd33c779ff83c80a.png

  1. Le code XML se présente comme suit :

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>

Comme @id/start, ce ConstraintSet comporte un seul Constraint sur @id/red_star. Cette fois, il le contraint à la partie inférieure de l'écran.

Vous n'êtes pas obligé de les nommer @id/start et @id/end, mais c'est pratique.

Étape 4 : Définissez une transition

Chaque MotionScene doit également inclure au moins une transition. Une transition définit chaque partie d'une animation, du début à la fin.

Une transition doit spécifier un ConstraintSet de début et de fin. Une transition peut également spécifier d'autres façons de modifier l'animation, par exemple la durée d'exécution de l'animation ou la façon d'animer en faisant glisser des vues.

  1. Motion Editor a créé une transition par défaut lorsque le fichier MotionScene a été créé. Ouvrez activity_step1_scene.xml pour afficher la transition générée.

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>

C'est tout ce dont MotionLayout a besoin pour créer une animation. Examinons chaque attribut :

  • constraintSetStart sera appliqué aux vues au début de l'animation.
  • constraintSetEnd sera appliqué aux vues à la fin de l'animation.
  • duration indique la durée de l'animation en millisecondes.

MotionLayout trouvera ensuite un chemin entre les contraintes de début et de fin, et l'animera pendant la durée spécifiée.

Étape 5 : Prévisualisez l'animation dans l'éditeur de mouvement

dff9ecdc1f4a0740.gif

Animation : vidéo montrant la prévisualisation d'une transition dans l'éditeur de mouvements

  1. Ouvrez l'éditeur de mouvement et sélectionnez la transition en cliquant sur la flèche entre start et end dans le panneau "Vue d'ensemble".

1dc541ae8c43b250.png

  1. Le panneau Sélection affiche les commandes de lecture et une barre de défilement lorsqu'une transition est sélectionnée. Cliquez sur le bouton de lecture ou faites glisser la position actuelle pour prévisualiser l'animation.

a0fd2593384dfb36.png

Étape 6 : Ajoutez un gestionnaire de clics

Vous avez besoin d'un moyen de démarrer l'animation. Pour ce faire, vous pouvez configurer MotionLayout pour qu'il réponde aux événements de clic sur @id/red_star.

  1. Ouvrez l'éditeur de mouvement et sélectionnez la transition en cliquant sur la flèche entre le début et la fin dans le panneau "Vue d'ensemble".

b6f94b344ce65290.png

  1. Cliquez sur 699f7ae04024ccf6.png Créer un gestionnaire de clics ou de balayages dans la barre d'outils du panneau "Aperçu". Cela ajoute un gestionnaire qui lancera une transition.
  2. Sélectionnez Click Handler (Gestionnaire de clics) dans le pop-up.

ccf92d06335105fe.png

  1. Définissez Vue à clic sur red_star.

b0d3f0c970604f01.png

  1. Cliquez sur Ajouter. Le gestionnaire de clics est représenté par un petit point dans l'éditeur de mouvement de la transition.

cec3913e67fb4105.png

  1. Sélectionnez la transition dans le panneau "Aperçu", puis ajoutez un attribut clickAction de toggle au gestionnaire OnClick que vous venez d'ajouter dans le panneau "Attributs".

9af6fc60673d093d.png

  1. Ouvrez activity_step1_scene.xml pour afficher le code généré par l'éditeur de mouvements.

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 indique à MotionLayout d'exécuter l'animation en réponse aux événements de clic à l'aide d'une balise <OnClick>. Examinons chaque attribut :

  • targetId est la vue à surveiller pour les clics.
  • clickAction de toggle bascule entre l'état de début et l'état de fin au clic. Vous trouverez d'autres options pour clickAction dans la documentation.
  1. Exécutez votre code, cliquez sur Étape 1, puis sur l'étoile rouge pour voir l'animation !

Étape 5 : Les animations en action

Exécutez l'application ! Vous devriez voir votre animation s'exécuter lorsque vous cliquez sur l'étoile.

7ba88af963fdfe10.gif

Le fichier de scène de mouvement terminé définit un Transition qui pointe vers un ConstraintSet de début et de fin.

Au début de l'animation (@id/start), l'icône en forme d'étoile est contrainte au début du haut de l'écran. À la fin de l'animation (@id/end), l'icône en forme d'étoile est contrainte à la fin inférieure de l'écran.

<?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. Animer en fonction des événements de déplacement

Pour cette étape, vous allez créer une animation qui répond à un événement de déplacement de l'utilisateur (lorsque l'utilisateur balaye l'écran) pour exécuter l'animation. MotionLayout permet de suivre les événements tactiles pour déplacer les vues, ainsi que les gestes de balayage basés sur la physique pour rendre le mouvement fluide.

Étape 1 : Inspecter le code initial

  1. Pour commencer, ouvrez le fichier de mise en page activity_step2.xml, qui contient un MotionLayout existant. Examinons le code.

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>

Cette mise en page définit toutes les vues de l'animation. Les trois icônes en forme d'étoile ne sont pas contraintes dans la mise en page, car elles seront animées dans la scène de mouvement.

Des contraintes sont appliquées aux crédits TextView, car ils restent au même endroit pendant toute l'animation et ne modifient aucun attribut.

Étape 2 : Animer la scène

Comme pour la dernière animation, l'animation sera définie par un ConstraintSet, de début et de fin, et un Transition.

Définissez le ConstraintSet de début.

  1. Ouvrez la scène de mouvement xml/step2.xml pour définir l'animation.
  2. Ajoutez les contraintes pour la contrainte de départ start. Au début, les trois étoiles sont centrées en bas de l'écran. Les étoiles de droite et de gauche ont une valeur alpha de 0.0, ce qui signifie qu'elles sont totalement transparentes et masquées.

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>

Dans ce ConstraintSet, vous spécifiez un Constraint pour chacune des étoiles. Chaque contrainte sera appliquée par MotionLayout au début de l'animation.

Chaque vue d'étoile est centrée en bas de l'écran à l'aide de contraintes de début, de fin et de bas. Les deux étoiles @id/left_star et @id/right_star ont une valeur alpha supplémentaire qui les rend invisibles et qui sera appliquée au début de l'animation.

Les ensembles de contraintes start et end définissent le début et la fin de l'animation. Une contrainte sur le début, comme motion:layout_constraintStart_toStartOf, contraindra le début d'une vue à celui d'une autre vue. Cela peut être déroutant au premier abord, car le nom start est utilisé à la fois pour et, et les deux sont utilisés dans le contexte des contraintes. Pour mieux comprendre la différence, le start dans layout_constraintStart fait référence au "début" de la vue, qui est à gauche dans une langue qui se lit de gauche à droite et à droite dans une langue qui se lit de droite à gauche. L'ensemble de contraintes start fait référence au début de l'animation.

Définir le ConstraintSet de fin

  1. Définissez la contrainte de fin pour utiliser une chaîne afin de positionner les trois étoiles ensemble sous @id/credits. De plus, il définira la valeur de fin de alpha des étoiles de gauche et de droite sur 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>

Le résultat final est que les vues s'étalent et s'élèvent à partir du centre lors de l'animation.

De plus, comme la propriété alpha est définie sur @id/right_start et @id/left_star dans ConstraintSets, les deux vues s'afficheront progressivement au fur et à mesure de l'animation.

Animer en fonction du balayage de l'utilisateur

MotionLayout peut suivre les événements de déplacement de l'utilisateur ou un balayage pour créer une animation de "lancer" basée sur la physique. Cela signifie que les vues continueront à défiler si l'utilisateur les fait glisser et ralentiront comme un objet physique qui roule sur une surface. Vous pouvez ajouter ce type d'animation avec une balise OnSwipe dans Transition.

  1. Remplacez le TODO pour ajouter un tag OnSwipe par <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 contient quelques attributs, dont le plus important est touchAnchorId.

  • touchAnchorId est la vue suivie qui se déplace en réponse au toucher. MotionLayout conservera cette vue à la même distance du doigt qui effectue le balayage.
  • touchAnchorSide détermine le côté de la vue à suivre. Cela est important pour les vues qui sont redimensionnées, qui suivent des chemins complexes ou dont un côté se déplace plus rapidement que l'autre.
  • dragDirection détermine la direction qui compte pour cette animation (vers le haut, le bas, la gauche ou la droite).

Lorsque MotionLayout écoute les événements de déplacement, l'écouteur est enregistré sur la vue MotionLayout et non sur celle spécifiée par touchAnchorId. Lorsqu'un utilisateur commence un geste n'importe où sur l'écran, MotionLayout maintient constante la distance entre son doigt et le touchAnchorSide de la vue touchAnchorId. Par exemple, s'ils appuient à 100 dp de la bordure d'ancrage, MotionLayout maintiendra cette bordure à 100 dp de leur doigt pendant toute l'animation.

Essayer

  1. Exécutez de nouveau l'application et ouvrez l'écran de l'étape 2. L'animation s'affiche.
  2. Essayez de faire glisser votre doigt ou de le relâcher à mi-chemin de l'animation pour découvrir comment MotionLayout affiche des animations fluides basées sur la physique.

fefcdd690a0dcaec.gif

MotionLayout peut animer des designs très différents à l'aide des fonctionnalités de ConstraintLayout pour créer des effets riches.

Dans cette animation, les trois vues sont positionnées par rapport à leur parent en bas de l'écran au début. À la fin, les trois vues sont positionnées par rapport à @id/credits dans une chaîne.

Malgré ces mises en page très différentes, MotionLayout créera une animation fluide entre le début et la fin.

5. Modifier un chemin d'accès

Dans cette étape, vous allez créer une animation qui suit un chemin complexe et anime les crédits pendant le mouvement. MotionLayout peut modifier le chemin qu'une vue empruntera entre le début et la fin à l'aide d'un KeyPosition.

Étape 1 : Explorez le code existant

  1. Ouvrez layout/activity_step3.xml et xml/step3.xml pour afficher la mise en page et la scène de mouvement existantes. Les ImageView et TextView affichent la lune et le texte des crédits.
  2. Ouvrez le fichier de scène de mouvement (xml/step3.xml). Vous voyez qu'un Transition de @id/start à @id/end est défini. L'animation déplace l'image de la lune du bas à gauche de l'écran vers le bas à droite de l'écran à l'aide de deux ConstraintSets. Le texte des crédits apparaît progressivement de alpha="0.0" à alpha="1.0" à mesure que la lune se déplace.
  3. Exécutez l'application maintenant et sélectionnez Étape 3. Vous verrez que la Lune suit une trajectoire linéaire (ou une ligne droite) du début à la fin lorsque vous cliquez dessus.

Étape 2 : Activez le débogage des chemins

Avant d'ajouter un arc au mouvement de la lune, il est utile d'activer le débogage du chemin dans MotionLayout.

Pour vous aider à développer des animations complexes avec MotionLayout, vous pouvez dessiner le chemin d'animation de chaque vue. Cela est utile lorsque vous souhaitez visualiser votre animation et affiner les petits détails du mouvement.

  1. Pour activer les chemins de débogage, ouvrez layout/activity_step3.xml et ajoutez motion:motionDebug="SHOW_PATH" à la balise MotionLayout.

activity_step3.xml

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

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

Une fois le débogage de chemin activé, lorsque vous exécutez à nouveau l'application, les chemins de toutes les vues sont visualisés avec une ligne en pointillés.

23bbb604f456f65c.png

  • Les cercles représentent la position de début ou de fin d'une vue.
  • Les lignes représentent le chemin d'une vue.
  • Les losanges représentent un KeyPosition qui modifie le chemin.

Par exemple, dans cette animation, le cercle du milieu correspond à la position du texte des crédits.

Étape 3 : Modifiez un chemin d'accès

Toutes les animations de MotionLayout sont définies par un ConstraintSet de début et de fin qui définit l'apparence de l'écran avant le début de l'animation et après sa fin. Par défaut, MotionLayout trace un chemin linéaire (une ligne droite) entre la position de départ et la position de fin de chaque vue dont la position change.

Pour créer des chemins complexes comme l'arc de la lune dans cet exemple, MotionLayout utilise un KeyPosition pour modifier le chemin emprunté par une vue entre le début et la fin.

  1. Ouvrez xml/step3.xml et ajoutez un KeyPosition à la scène. La balise KeyPosition est placée à l'intérieur de la balise 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>

Un KeyFrameSet est un enfant d'un Transition. Il s'agit d'un ensemble de tous les KeyFrames, tels que KeyPosition, qui doivent être appliqués pendant la transition.

Comme MotionLayout calcule le chemin de la lune entre le début et la fin, il modifie le chemin en fonction de KeyPosition spécifié dans KeyFrameSet. Vous pouvez voir comment cela modifie le chemin en exécutant à nouveau l'application.

Un KeyPosition possède plusieurs attributs qui décrivent la façon dont il modifie le chemin d'accès. Voici les plus importants :

  • framePosition est un nombre compris entre 0 et 100. Il définit le moment où ce KeyPosition doit être appliqué dans l'animation, 1 correspondant à 1 % de l'animation et 99 à 99 %. Si la valeur est de 50, vous l'appliquez au milieu.
  • motionTarget est la vue pour laquelle ce KeyPosition modifie le chemin.
  • keyPositionType indique comment KeyPosition modifie le chemin d'accès. Il peut s'agir de parentRelative, pathRelative ou deltaRelative (comme expliqué à l'étape suivante).
  • percentX | percentY correspond à la valeur de modification du chemin à framePosition (valeurs comprises entre 0 et 1, avec des valeurs négatives et des valeurs supérieures à 1 autorisées).

Vous pouvez le voir comme suit : "À framePosition, modifiez le chemin d'accès de motionTarget en le déplaçant de percentX ou percentY selon les coordonnées déterminées par keyPositionType."

Par défaut, MotionLayout arrondit tous les angles créés en modifiant le chemin d'accès. Si vous regardez l'animation que vous venez de créer, vous pouvez voir que la lune suit une trajectoire courbe au niveau de l'angle. Dans la plupart des cas, c'est ce que vous souhaitez. Si ce n'est pas le cas, vous pouvez spécifier l'attribut curveFit pour le personnaliser.

Essayer

Si vous exécutez à nouveau l'application, vous verrez l'animation pour cette étape.

46b179c01801f19e.gif

La lune suit un arc, car elle passe par un KeyPosition spécifié dans Transition.

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

Vous pouvez lire ce KeyPosition comme suit : "À framePosition 50 (à mi-chemin de l'animation), modifiez le chemin d'accès de motionTarget @id/moon en le déplaçant de 50% Y (à mi-hauteur de l'écran) selon les coordonnées déterminées par parentRelative (l'ensemble de MotionLayout)."

Ainsi, à mi-chemin de l'animation, la lune doit passer par un KeyPosition qui se trouve à 50 % en bas de l'écran. Ce KeyPosition ne modifie en rien le mouvement X. La lune ira donc toujours d'un bout à l'autre horizontalement. MotionLayout trouvera un chemin fluide qui passe par ce KeyPosition tout en se déplaçant entre le début et la fin.

Si vous regardez attentivement, le texte des crédits est contraint par la position de la lune. Pourquoi ne se déplace-t-il pas aussi verticalement ?

1c7cf779931e45cc.gif

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

Il s'avère que même si vous modifiez la trajectoire de la Lune, ses positions de départ et d'arrivée ne la déplacent pas du tout verticalement. KeyPosition ne modifie pas la position de début ni celle de fin. Le texte des crédits est donc limité à la position de fin de la lune.

Si vous souhaitez que les crédits se déplacent avec la lune, vous pouvez ajouter un KeyPosition aux crédits ou modifier les contraintes de début sur @id/credits.

Dans la section suivante, vous allez découvrir les différents types de keyPositionType dans MotionLayout.

6. Comprendre keyPositionType

À la dernière étape, vous avez utilisé un parentRelative de type keyPosition pour décaler le chemin de 50 % de l'écran. L'attribut keyPositionType détermine comment MotionLayout modifiera le chemin en fonction de percentX ou percentY.

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

Il existe trois types de keyPosition : parentRelative, pathRelative et deltaRelative. Si vous spécifiez un type, le système de coordonnées utilisé pour calculer percentX et percentY sera modifié.

Qu'est-ce qu'un système de coordonnées ?

Un système de coordonnées permet de spécifier un point dans l'espace. Elles sont également utiles pour décrire une position à l'écran.

Les systèmes de coordonnées MotionLayout sont des systèmes de coordonnées cartésiennes. Cela signifie qu'ils ont un axe X et un axe Y définis par deux lignes perpendiculaires. La principale différence entre eux réside dans la position de l'axe X sur l'écran (l'axe Y est toujours perpendiculaire à l'axe X).

Tous les systèmes de coordonnées de MotionLayout utilisent des valeurs comprises entre 0.0 et 1.0 sur les axes X et Y. Elles autorisent les valeurs négatives et les valeurs supérieures à 1.0. Par exemple, une valeur percentX de -2.0 signifie "aller dans la direction opposée de l'axe X deux fois".

Si tout cela vous rappelle un peu trop vos cours d'algèbre, consultez les images ci-dessous.

Coordonnées parentRelative

a7b7568d46d9dec7.png

Le keyPositionType de parentRelative utilise le même système de coordonnées que l'écran. Il définit (0, 0) en haut à gauche de l'ensemble de MotionLayout et (1, 1) en bas à droite.

Vous pouvez utiliser parentRelative chaque fois que vous souhaitez créer une animation qui se déplace sur l'ensemble de MotionLayout, comme l'arc de lune dans cet exemple.

Toutefois, si vous souhaitez modifier un chemin par rapport au mouvement, par exemple pour le rendre légèrement incurvé, les deux autres systèmes de coordonnées sont plus adaptés.

Coordonnées delta relatives

5680bf553627416c.png

Delta est un terme mathématique qui signifie "variation". deltaRelative signifie donc "variation relative". Dans les coordonnées deltaRelative, (0,0) correspond à la position de départ de la vue et (1,1) à la position de fin. Les axes X et Y sont alignés sur l'écran.

L'axe X est toujours horizontal sur l'écran, et l'axe Y est toujours vertical. Par rapport à parentRelative, la principale différence est que les coordonnées décrivent uniquement la partie de l'écran dans laquelle la vue se déplacera.

deltaRelative est un excellent système de coordonnées pour contrôler le mouvement horizontal ou vertical de manière isolée. Par exemple, vous pouvez créer une animation qui ne termine son mouvement vertical (Y) qu'à 50 % et continue de s'animer horizontalement (X).

pathRelative coordinates

f3aaadaac8b4a93f.png

Le dernier système de coordonnées dans MotionLayout est pathRelative. Elle est très différente des deux autres, car l'axe X suit la trajectoire d'animation du début à la fin. (0,0) est la position de départ et (1,0) est la position de fin.

Pourquoi faire cela ? Cela peut paraître surprenant au premier abord, d'autant plus que ce système de coordonnées n'est même pas aligné sur le système de coordonnées de l'écran.

Il s'avère que pathRelative est très utile pour plusieurs choses.

  • Accélérer, ralentir ou arrêter une vue pendant une partie de l'animation Étant donné que la dimension X correspond toujours exactement au chemin emprunté par la vue, vous pouvez utiliser un pathRelative KeyPosition pour modifier le framePosition auquel un point spécifique de ce chemin est atteint. Ainsi, un KeyPosition à framePosition="50" avec un percentX="0.1" fera que l'animation prendra 50 % du temps pour parcourir les 10 % du mouvement.
  • Ajouter un arc subtil à un chemin d'accès. Étant donné que la dimension Y est toujours perpendiculaire au mouvement, la modification de Y entraînera une modification de la trajectoire, qui deviendra courbe par rapport au mouvement global.
  • Vous ne pouvez pas ajouter de deuxième dimension lorsque deltaRelative est sélectionné. Pour un mouvement complètement horizontal ou vertical, deltaRelative ne créera qu'une seule dimension utile. Toutefois, pathRelative créera toujours des coordonnées X et Y utilisables.

Dans l'étape suivante, vous allez apprendre à créer des chemins encore plus complexes en utilisant plusieurs KeyPosition.

7. Créer des chemins complexes

En regardant l'animation que vous avez créée à l'étape précédente, vous constatez qu'elle crée une courbe lisse, mais que la forme pourrait être plus "lunaire".

Modifier un chemin comportant plusieurs éléments KeyPosition

MotionLayout peut modifier davantage un chemin en définissant autant de KeyPosition que nécessaire pour obtenir n'importe quel mouvement. Pour cette animation, vous allez créer un arc, mais vous pourriez faire sauter la lune de haut en bas au milieu de l'écran, si vous le souhaitez.

  1. Ouvrez xml/step4.xml. Vous pouvez constater qu'il comporte les mêmes vues et le KeyFrame que vous avez ajouté à la dernière étape.
  2. Pour arrondir le haut de la courbe, ajoutez deux autres KeyPositions au chemin de @id/moon, l'un juste avant qu'il n'atteigne le sommet et l'autre après.

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"
/>

Ces KeyPositions seront appliqués à 25 % et 75 % de l'animation, et feront passer @id/moon par un chemin situé à 60 % du haut de l'écran. Combiné à la KeyPosition existante à 50 %, cela crée un arc lisse que la lune peut suivre.

Dans MotionLayout, vous pouvez ajouter autant de KeyPositions que nécessaire pour obtenir la trajectoire souhaitée. MotionLayout appliquera chaque KeyPosition à la framePosition spécifiée et trouvera comment créer un mouvement fluide qui passe par tous les KeyPositions.

Essayer

  1. Exécutez à nouveau l'application. Accédez à l'étape 4 pour voir l'animation en action. Lorsque vous cliquez sur la lune, elle suit le chemin du début à la fin, en passant par chaque KeyPosition spécifié dans le KeyFrameSet.

Explorer par vous-même

Avant de passer à d'autres types de KeyFrame, essayez d'ajouter d'autres KeyPositions à KeyFrameSet pour voir quels types d'effets vous pouvez créer en utilisant uniquement KeyPosition.

Voici un exemple montrant comment créer un chemin complexe qui se déplace d'avant en arrière pendant l'animation.

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>

Une fois que vous avez terminé d'explorer KeyPosition, vous passerez à d'autres types de KeyFrames à l'étape suivante.

8. Modifier les attributs pendant le mouvement

Pour créer des animations dynamiques, il faut souvent modifier les size, rotation ou alpha des vues à mesure que l'animation progresse. MotionLayout permet d'animer de nombreux attributs sur n'importe quelle vue à l'aide d'un KeyAttribute.

Dans cette étape, vous allez utiliser KeyAttribute pour mettre à l'échelle et faire pivoter la lune. Vous utiliserez également KeyAttribute pour retarder l'apparition du texte jusqu'à ce que la lune ait presque terminé son voyage.

Étape 1 : Redimensionnez et faites pivoter avec KeyAttribute

  1. Ouvrez xml/step5.xml, qui contient la même animation que celle que vous avez créée à l'étape précédente. Pour varier les plaisirs, cet écran utilise une autre image de l'espace comme arrière-plan.
  2. Pour que la lune grandisse et tourne, ajoutez deux balises KeyAttribute dans KeyFrameSet à keyFrame="50" et 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"
/>

Ces KeyAttributes sont appliqués à 50 % et 100 % de l'animation. Le premier KeyAttribute à 50 % se produit en haut de l'arc et entraîne un doublement de la taille de la vue, ainsi qu'une rotation de -360 degrés (ou un cercle complet). Le deuxième KeyAttribute terminera la deuxième rotation à -720 degrés (deux cercles complets) et réduira la taille à la normale, car les valeurs scaleX et scaleY sont définies par défaut sur 1.0.

Comme un KeyPosition, un KeyAttribute utilise framePosition et motionTarget pour spécifier quand appliquer le KeyFrame et quelle vue modifier. MotionLayout effectuera une interpolation entre KeyPositions pour créer des animations fluides.

KeyAttributes accepte les attributs qui peuvent être appliqués à toutes les vues. Ils permettent de modifier des attributs de base tels que visibility, alpha ou elevation. Vous pouvez également modifier la rotation comme vous le faites ici, faire pivoter en trois dimensions avec rotateX et rotateY, mettre à l'échelle la taille avec scaleX et scaleY, ou traduire la position de la vue en X, Y ou Z.

Étape 2 : Retardez l'affichage des crédits

L'un des objectifs de cette étape est de mettre à jour l'animation afin que le texte des crédits n'apparaisse qu'une fois l'animation presque terminée.

  1. Pour retarder l'apparition des crédits, définissez un autre KeyAttribute qui garantit que alpha restera à 0 jusqu'à keyPosition="85". MotionLayout passera toujours en douceur de 0 à 100 alpha, mais sur les 15 derniers pour cent de l'animation.

step5.xml

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

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

Ce KeyAttribute maintient le alpha de @id/credits à 0,0 pour les 85 premiers pour cent de l'animation. Comme il commence avec un canal alpha de 0, cela signifie qu'il sera invisible pendant les 85 premiers pour cent de l'animation.

L'effet final de ce KeyAttribute est que les crédits apparaissent vers la fin de l'animation. Cela donne l'impression qu'ils sont coordonnés avec la lune qui se couche dans l'angle droit de l'écran.

En retardant les animations sur une vue pendant qu'une autre vue se déplace de cette manière, vous pouvez créer des animations impressionnantes qui semblent dynamiques pour l'utilisateur.

Essayer

  1. Exécutez à nouveau l'application et passez à l'étape 5 pour voir l'animation en action. Lorsque vous cliquez sur la lune, elle suit le chemin du début à la fin, en passant par chaque KeyAttribute spécifié dans le KeyFrameSet.

2f4bfdd681c1fa98.gif

Comme vous faites faire à la lune deux tours complets, elle effectue un double salto arrière et les crédits n'apparaissent qu'à la fin de l'animation.

Explorer par vous-même

Avant de passer au dernier type de KeyFrame, essayez de modifier d'autres attributs standards dans KeyAttributes. Par exemple, essayez de remplacer rotation par rotationX pour voir l'animation produite.

Voici une liste des attributs standards que vous pouvez essayer :

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

9. Modifier les attributs personnalisés

Les animations riches consistent à modifier la couleur ou d'autres attributs d'une vue. Alors que MotionLayout peut utiliser un KeyAttribute pour modifier l'un des attributs standards listés dans la tâche précédente, vous utilisez un CustomAttribute pour spécifier tout autre attribut.

Un CustomAttribute peut être utilisé pour définir n'importe quelle valeur disposant d'un setter. Par exemple, vous pouvez définir backgroundColor sur une vue à l'aide d'un CustomAttribute. MotionLayout utilisera la réflexion pour trouver le setter, puis l'appellera à plusieurs reprises pour animer la vue.

Au cours de cette étape, vous allez utiliser CustomAttribute pour définir l'attribut colorFilter sur la lune afin de créer l'animation ci-dessous.

5fb6792126a09fda.gif

Définir des attributs personnalisés

  1. Pour commencer, ouvrez xml/step6.xml, qui contient la même animation que celle que vous avez créée à l'étape précédente.
  2. Pour que la lune change de couleur, ajoutez deux KeyAttribute avec un CustomAttribute dans le KeyFrameSet à keyFrame="0", keyFrame="50" et 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>

Vous ajoutez un CustomAttribute dans un KeyAttribute. Le CustomAttribute sera appliqué au framePosition spécifié par KeyAttribute.

Dans CustomAttribute, vous devez spécifier un attributeName et une valeur à définir.

  • motion:attributeName est le nom du setter qui sera appelé par cet attribut personnalisé. Dans cet exemple, setColorFilter sur Drawable sera appelé.
  • motion:custom*Value est une valeur personnalisée du type indiqué dans le nom. Dans cet exemple, la valeur personnalisée est une couleur spécifiée.

Les valeurs personnalisées peuvent être de l'un des types suivants :

  • Couleur
  • Nombre entier
  • Float
  • Chaîne
  • Dimension
  • Booléen

Grâce à cette API, MotionLayout peut animer tout élément qui fournit un setter sur une vue.

Essayer

  1. Exécutez de nouveau l'application et passez à l'étape 6 pour voir l'animation en action. Lorsque vous cliquez sur la lune, elle suit le chemin du début à la fin, en passant par chaque KeyAttribute spécifié dans le KeyFrameSet.

5fb6792126a09fda.gif

Lorsque vous ajoutez d'autres KeyFrames, MotionLayout modifie la trajectoire de la lune, qui passe d'une ligne droite à une courbe complexe, en ajoutant un double salto arrière, un redimensionnement et un changement de couleur à mi-parcours de l'animation.

Dans les animations réelles, vous animez souvent plusieurs vues en même temps, en contrôlant leur mouvement selon différents chemins et vitesses. En spécifiant un KeyFrame différent pour chaque vue, il est possible de chorégraphier des animations riches qui animent plusieurs vues avec MotionLayout.

10. Événements de déplacement et chemins complexes

Dans cette étape, vous allez découvrir comment utiliser OnSwipe avec des chemins complexes. Jusqu'à présent, l'animation de la lune était déclenchée par un écouteur OnClick et s'exécutait pendant une durée fixe.

Pour contrôler les animations dont les chemins sont complexes à l'aide de OnSwipe, comme l'animation de la lune que vous avez créée au cours des dernières étapes, vous devez comprendre comment fonctionne OnSwipe.

Étape 1 : Explorer le comportement OnSwipe

  1. Ouvrez xml/step7.xml et recherchez la déclaration OnSwipe existante.

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide →

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
/>
  1. Exécutez l'application sur votre appareil et passez à l'étape 7. Voyez si vous pouvez créer une animation fluide en faisant glisser la lune le long de l'arc.

Lorsque vous exécutez cette animation, le résultat n'est pas très satisfaisant. Une fois que la lune atteint le sommet de l'arc, elle commence à sauter.

ed96e3674854a548.gif

Pour comprendre le bug, imaginez ce qui se passe lorsque l'utilisateur appuie juste en dessous du haut de l'arc. Étant donné que la balise OnSwipe comporte un motion:touchAnchorSide="bottom", MotionLayout tentera de maintenir la distance entre le doigt et le bas de la vue constante tout au long de l'animation.

Toutefois, comme le bas de la lune ne va pas toujours dans la même direction (il monte, puis redescend), MotionLayout ne sait pas quoi faire lorsque l'utilisateur vient de dépasser le haut de l'arc. Pour ce faire, puisque vous suivez le bas de la lune, où doit-il être placé lorsque l'utilisateur appuie ici ?

56cd575c5c77eddd.png

Étape 2 : Utilisez le côté droit

Pour éviter ce type de bug, il est important de toujours choisir un touchAnchorId et un touchAnchorSide qui progressent toujours dans une seule direction pendant toute la durée de l'animation.

Dans cette animation, les côtés right et left de la lune progressent à l'écran dans une même direction.

Toutefois, bottom et top inverseront leur direction. Lorsque OnSwipe tente de les suivre, il est dérouté lorsque leur direction change.

  1. Pour que cette animation suive les événements tactiles, remplacez touchAnchorSide par right.

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide →

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

Étape 3 : Utilisez dragDirection

Vous pouvez également combiner dragDirection avec touchAnchorSide pour que la voie de service prenne une direction différente de celle qu'elle prendrait normalement. Il est toujours important que touchAnchorSide ne progresse que dans une seule direction, mais vous pouvez indiquer à MotionLayout la direction à suivre. Par exemple, vous pouvez conserver touchAnchorSide="bottom", mais ajouter dragDirection="dragRight". MotionLayout suivra alors la position du bas de la vue, mais ne tiendra compte de son emplacement que lors d'un déplacement vers la droite (il ignore les mouvements verticaux). Ainsi, même si le bas monte et descend, il s'animera correctement avec OnSwipe.

  1. Mettez à jour OnSwipe pour suivre correctement le mouvement de la lune.

step7.xml

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

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

Essayer

  1. Exécutez à nouveau l'application et essayez de faire glisser la lune sur toute la trajectoire. Même si elle suit un arc complexe, MotionLayout pourra faire progresser l'animation en réponse aux événements de balayage.

5458dff382261427.gif

11. Exécuter le mouvement avec du code

MotionLayout peut être utilisé pour créer des animations riches lorsqu'il est associé à CoordinatorLayout. Au cours de cette étape, vous allez créer un en-tête réductible à l'aide de MotionLayout.

Étape 1 : Explorez le code existant

  1. Pour commencer, ouvrez layout/activity_step8.xml.
  2. Dans layout/activity_step8.xml, vous pouvez voir qu'un CoordinatorLayout et un AppBarLayout fonctionnels sont déjà intégrés.

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>

Cette mise en page utilise un CoordinatorLayout pour partager les informations de défilement entre NestedScrollView et AppBarLayout. Ainsi, lorsque NestedScrollView défile vers le haut, il informe AppBarLayout du changement. C'est ainsi que vous implémentez une barre d'outils réductible comme celle-ci sur Android : le défilement du texte sera "coordonné" avec l'en-tête réductible.

La scène de mouvement vers laquelle @id/motion_layout pointe est semblable à celle de la dernière étape. Toutefois, la déclaration OnSwipe a été supprimée pour permettre à CoordinatorLayout de fonctionner.

  1. Exécutez l'application et accédez à l'étape 8. Vous remarquerez que lorsque vous faites défiler le texte, la lune ne bouge pas.

Étape 2 : Faire défiler le MotionLayout

  1. Pour que la vue MotionLayout défile dès que NestedScrollView défile, ajoutez motion:minHeight et motion:layout_scrollFlags à 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. Exécutez à nouveau l'application et passez à l'étape 8. Vous pouvez voir que MotionLayout se réduit lorsque vous faites défiler la page vers le haut. Toutefois, l'animation ne progresse pas encore en fonction du comportement de défilement.

Étape 3 : Déplacez le mouvement avec du code

  1. Ouvrez Step8Activity.kt . Modifiez la fonction coordinateMotion() pour informer MotionLayout des modifications de la position de défilement.

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)
}

Ce code enregistre un OnOffsetChangedListener qui sera appelé chaque fois que l'utilisateur fera défiler l'écran avec le décalage de défilement actuel.

MotionLayout permet de rechercher sa transition en définissant la propriété de progression. Pour convertir une verticalOffset en pourcentage de progression, divisez-la par la plage de défilement totale.

Essayer

  1. Déployez à nouveau l'application et exécutez l'animation de l'étape 8. Vous voyez que MotionLayout fait progresser l'animation en fonction de la position de défilement.

ee5ce4d9e33a59ca.gif

Il est possible de créer des animations personnalisées de barre d'outils réductible dynamique à l'aide de MotionLayout. En utilisant une séquence de KeyFrames, vous pouvez obtenir des effets très gras.

12. Félicitations

Cet atelier de programmation a abordé l'API de base de MotionLayout.

Pour voir d'autres exemples de MotionLayout en pratique, consultez l'exemple officiel. N'oubliez pas de consulter la documentation.

En savoir plus

MotionLayout est compatible avec encore plus de fonctionnalités qui ne sont pas abordées dans cet atelier de programmation, comme KeyCycle, qui vous permet de contrôler les chemins ou les attributs avec des cycles répétitifs, et KeyTimeCycle, qui vous permet d'animer en fonction de l'heure. Consultez les exemples pour voir comment les utiliser.

Pour accéder aux autres ateliers de programmation de ce cours, consultez la page de destination des ateliers de programmation Android avancé en Kotlin.