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 si vous suivez 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 avancés pour Android en Kotlin.

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

Vous pouvez utiliser MotionLayout pour animer simultanément l'emplacement, la taille, la visibilité, la valeur alpha, la couleur, l'élévation, la rotation et d'autres attributs de plusieurs vues. À l'aide du code XML déclaratif, vous pouvez créer des animations coordonnées impliquant plusieurs vues, qui sont difficiles à réaliser avec du code.

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 permet à l'utilisateur de suivre naturellement les modifications apportées à votre interface utilisateur.
  • Attirez l'attention : utilisez des animations pour attirer l'attention sur des éléments importants de l'interface utilisateur.
  • Créez de magnifiques graphismes : des mouvements de conception efficaces rendent les applications plus attrayantes.

Prérequis

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

  • Apprenez à 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 la mise en page avec contrainte.

Objectifs de l'atelier

  • Définir une animation avec ConstraintSets et MotionLayout
  • Animation basée sur des événements de déplacement
  • Modifier l'animation avec KeyPosition
  • Modifier les attributs avec KeyAttribute
  • Exécuter des animations avec du code
  • Animer des 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

Tout d'abord, vous allez créer une animation qui déplace une vue du début vers le bas de l'écran en réponse à un clic de l'utilisateur.

Pour créer une animation à partir du code de démarrage, vous avez 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 qui décrit une animation pour MotionLayout.
  • Transition, qui fait partie de MotionScene qui spécifie la durée de l'animation, le déclencheur et comment déplacer les vues.
  • Élément ConstraintSet qui spécifie à la fois les contraintes start et end de la transition.

Examinons chacun de ces éléments l'un après l'autre, en commençant par MotionLayout.

Étape 1: Explorez le code existant

MotionLayout est une sous-classe de ConstraintLayout. Il prend donc en charge les mêmes fonctionnalités lors de l'ajout d'une animation. Pour utiliser MotionLayout, vous ajoutez une vue MotionLayout où vous utiliseriez ConstraintLayout..

  1. Dans res/layout, ouvrez activity_step1.xml.. Vous avez ici un ConstraintLayout contenant un seul ImageView d'étoile, avec une teinte appliquée.

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écutiez l'application maintenant, vous verrez l'affichage des étoiles sans contrainte, ce qui signifie qu'ils seraient positionnés dans un emplacement inconnu. Android Studio affiche un avertissement concernant l'absence de contraintes.

Étape 2: Passer à la mise en page de mouvement

Pour créer une animation à l'aide de MotionLayout,, vous devez convertir ConstraintLayout en MotionLayout.

Pour que votre mise en page utilise une scène de mouvement, elle doit pointer vers elle.

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

a2beea710c2decb7.png

  1. Une fois que vous avez ouvert la surface de conception, effectuez un clic droit sur l'aperçu, puis 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 votre élément en MotionLayout, la surface de conception affiche l'éditeur de mouvement

66d0e80d5ab4daf8.png

L'Éditeur de mouvement comprend trois nouveaux éléments d'interface utilisateur:

  1. Overview (Aperçu) : cette sélection modale vous permet de sélectionner différentes parties de l'animation. Dans cette image, la ConstraintSet start est sélectionnée. Vous pouvez également sélectionner la transition entre start et end en cliquant sur la flèche entre les éléments.
  2. Section : sous l'aperçu, une fenêtre de section change en fonction de l'élément actuellement sélectionné. Dans cette image, les informations ConstraintSet start s'affichent dans la fenêtre de sélection.
  3. Attribut : le panneau des attributs s'affiche et vous permet de modifier les attributs de l'élément sélectionné depuis la fenêtre de présentation ou de sélection. Dans cette image, il s'agit des attributs du 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 ce à quoi ressemble l'écran avant l'animation et la fin décrit ce à quoi ressemble l'écran une fois l'animation terminée. MotionLayout est responsable de l'animation 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. Comme son nom l'indique, une ConstraintSet est un ensemble de contraintes qui peuvent être appliquées aux vues. Cela inclut les contraintes de largeur, de hauteur et de ConstraintLayout. Il inclut également certains attributs tels que alpha. Il ne contient pas les vues elles-mêmes, mais uniquement les contraintes qui s'appliquent à ces vues.

Toutes les contraintes spécifiées dans un ConstraintSet remplacent 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 celles de MotionScene sont appliquées.

Au cours de cette étape, vous allez faire en sorte que l'affichage des étoiles commence en haut au début de l'écran et se termine en bas de l'écran.

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

  1. Sélectionnez le ConstraintSet start dans le panneau de présentation

6e57661ed358b860.png

  1. Dans le panneau de sélection, sélectionnez red_star. La source s'affiche actuellement pour layout, ce qui signifie qu'il n'est pas limité dans cette 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 de start lorsque start ConstraintSet est sélectionné dans le panneau d'aperçu.
  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 mouvement 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>

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

Le Constraint dans 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 des contraintes et des 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 requises pour contraindre la vue red_star au début de son parent.

  1. Sélectionnez le ConstraintSet end dans le panneau de présentation.

346e1248639b6f1e.png

  1. Suivez la même procédure que précédemment pour ajouter un Constraint pour red_star dans le end ConstraintSet.
  2. Pour utiliser l'Éditeur de mouvement afin d'effectuer cette étape, ajoutez une contrainte à 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>

Tout comme @id/start, ce ConstraintSet possède un seul Constraint sur @id/red_star. Cette fois, l'application applique une contrainte sur l'extrémité inférieure de l'écran.

Il n'est pas nécessaire de les nommer @id/start et @id/end, mais cette opération 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 des ConstraintSet de début et de fin. Une transition peut également spécifier d'autres manières de modifier l'animation, par exemple sa durée d'exécution ou la manière de l'animer en faisant glisser des vues.

  1. L'Éditeur de mouvement crée une transition par défaut pour nous lors de la création du fichier MotionScene. Ouvrez activity_step1_scene.xml pour voir 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. Examiner chaque attribut:

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

MotionLayout recherche alors un chemin entre les contraintes de début et de fin et l'anime pendant la durée spécifiée.

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

dff9ecdc1f4a0740.gif

Animation:vidéo de lecture d'un aperçu de transition dans l'éditeur de mouvement

  1. Ouvrez l'Éditeur de mouvement, puis sélectionnez la transition en cliquant sur la flèche entre start et end dans le panneau de présentation.

1dc541ae8c43b250.png

  1. Le panneau selection (sélection) affiche les commandes de lecture et une barre de lecture 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 devez trouver un moyen de lancer l'animation. Pour ce faire, vous pouvez faire en sorte que MotionLayout 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 de présentation.

b6f94b344ce65290.png

  1. Cliquez sur 699f7ae04024ccf6.png Créer un gestionnaire de clics ou de balayages dans la barre d'outils du panneau de présentation . Cette action ajoute un gestionnaire qui lancera une transition.
  2. Sélectionnez Gestionnaire de clics dans la fenêtre pop-up.

ccf92d06335105fe.png

  1. Définissez View To Click (Afficher pour cliquer) sur red_star.

b0d3f0c970604f01.png

  1. Cliquez sur Ajouter. Dans l'Éditeur de mouvement, le gestionnaire de clics est représenté par un petit point au niveau de la transition.

cec3913e67fb4105.png

  1. Une fois la transition sélectionnée dans le panneau "Overview" (Aperçu), ajoutez un attribut clickAction de toggle au gestionnaire OnClick que vous venez d'ajouter dans le panneau des attributs.

9af6fc60673d093d.png

  1. Ouvrez activity_step1_scene.xml pour voir le code généré par l'Éditeur de mouvement

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>. Examiner chaque attribut:

  • targetId est la vue à surveiller pour les clics.
  • clickAction sur toggle passera de l'état de début à l'état de fin lors d'un clic. Vous pouvez découvrir d'autres options pour clickAction dans la documentation.
  1. Exécutez votre code, cliquez sur l'étape 1, puis sur l'étoile rouge et observez l'animation.

Étape 5: Les animations en action

Exécutez l'application ! Votre animation doit s'exécuter lorsque vous cliquez sur l'étoile.

7ba88af963fdfe10.gif

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

Au début de l'animation (@id/start), l'icône en forme d'étoile se limite au début supérieur de l'écran. À la fin de l'animation (@id/end), l'icône en forme d'étoile se trouve au bas 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. Animation basée sur des événements de déplacement

Au cours de cette étape, vous allez créer une animation qui répond à un événement de déplacement de l'utilisateur (lorsque l'utilisateur balaie l'écran) pour exécuter l'animation. MotionLayout prend en charge le suivi des événements tactiles pour déplacer les vues, ainsi que des gestes de glissement d'un geste vif basés sur les lois de la physique pour fluidifier le mouvement.

Étape 1: Inspectez le code initial

  1. Pour commencer, ouvrez le fichier de mise en page activity_step2.xml, qui contient déjà un MotionLayout. Examinez 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 icônes à trois étoiles ne sont pas limitées dans la mise en page, car elles sont animées dans la scène de mouvement.

Des contraintes sont appliquées au crédit TextView, car il reste au même endroit pendant toute l'animation et ne modifie aucun attribut.

Étape 2: Animez la scène

Comme la dernière animation, elle est définie par une ConstraintSet, de début et de fin, ainsi qu'une Transition.

Définit le ConstraintSet de départ.

  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 cette ConstraintSet, vous spécifiez une valeur Constraint pour chacune des étoiles. Chaque contrainte sera appliquée par MotionLayout au début de l'animation.

Chaque vue en étoile est centrée en bas de l'écran en fonction des contraintes de début, de fin et de bas. Les deux étoiles @id/left_star et @id/right_star ont toutes deux 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 appliquée au début (motion:layout_constraintStart_toStartOf, par exemple) contraint le début d'une vue au début d'une autre. Cela peut prêter à confusion au début, car le nom start est utilisé à la fois et dans le contexte de contraintes. Pour faire la distinction, le start dans layout_constraintStart fait référence au "début" de la vue, qui est celle de gauche dans une langue de gauche à droite et celle de droite dans une langue de droite à gauche. L'ensemble de contraintes start fait référence au début de l'animation.

Définir le ConstraintSet final

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

Au final, les vues s'étendent et s'étendent à partir du centre au fur et à mesure de leur animation.

De plus, comme la propriété alpha est définie sur @id/right_start et @id/left_star dans les deux vues ConstraintSets, les deux vues s'affichent en fondu à mesure que l'animation progresse.

Animation basée sur le balayage de l'utilisateur

MotionLayout peut suivre les événements de déplacement de l'utilisateur, ou un balayage, pour créer un "glissement d'un geste vif" basé sur les lois de la physique de l'animation. Cela signifie que les vues se poursuivront si l'utilisateur les déplace et ralentiront comme le ferait un objet physique lorsqu'il roulerait sur une surface. Vous pouvez ajouter ce type d'animation avec une balise OnSwipe dans Transition.

  1. Remplacez TODO pour ajouter une balise 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, le plus important étant touchAnchorId.

  • touchAnchorId est la vue suivie qui se déplace en réponse à un appui. MotionLayout maintient cette vue à la même distance du doigt qui balaie l'écran.
  • touchAnchorSide détermine quel côté de la vue doit être suivi. Ceci 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 de l'animation (vers le haut, le bas, la gauche ou la droite).

Lorsque MotionLayout écoute des événements de déplacement, l'écouteur est enregistré dans la vue MotionLayout, et non dans la vue 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, si l'utilisateur touche 100 dp du côté de l'ancre, MotionLayout l'éloigne de 100 dp de son doigt pendant toute l'animation.

Essayer

  1. Exécutez à nouveau l'application et ouvrez l'écran de l'étape 2. L'animation s'affiche.
  2. Essayez de faire glisser d'un geste vif Vous pouvez aussi relâcher le doigt au milieu de l'animation pour découvrir comment MotionLayout affiche des animations basées sur la physique des fluides.

fefcdd690a0dcaec.gif

MotionLayout peut animer des conceptions très différentes à 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 pour commencer. À 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ée une animation fluide entre le début et la fin.

5. Modifier un chemin d'accès

Au cours de cette étape, vous allez créer une animation qui suit un tracé complexe pendant l'animation et anime les crédits pendant le mouvement. MotionLayout peut modifier le chemin qu'une vue prendra 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 d'animation existantes. 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 constatez qu'un élément Transition de @id/start à @id/end est défini. L'animation déplace l'image de lune du coin inférieur gauche de l'écran vers le bas à droite de l'écran à l'aide de deux ConstraintSets. Le texte des crédits apparaît en fondu 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 un trajet linéaire (ou une ligne droite) du début à la fin lorsque vous cliquez dessus.

Étape 2: Activer le débogage du chemin d'accès

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

Pour développer des animations complexes avec MotionLayout, vous pouvez dessiner le chemin d'animation de chaque vue. Cette fonctionnalité est utile lorsque vous souhaitez visualiser votre animation et affiner les moindres 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" >

Après avoir activé le débogage du chemin d'accès, lorsque vous réexécuterez l'application, les chemins de toutes les vues s'afficheront à l'aide d'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 trajet d'une vue.
  • Les losanges représentent un KeyPosition qui modifie le tracé.

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

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

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

Pour construire des tracés complexes comme l'arc de la lune dans cet exemple, MotionLayout utilise un KeyPosition afin de modifier le trajet 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.

Étant donné que MotionLayout calcule le trajet de la Lune entre le début et la fin, il modifie le tracé en fonction de la valeur KeyPosition spécifiée dans le KeyFrameSet. Vous pouvez voir comment cela modifie le chemin d'accès en exécutant à nouveau l'application.

Un élément KeyPosition comporte plusieurs attributs qui décrivent la manière dont il modifie le chemin d'accès. Voici les plus importants:

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

Voici comment procéder : "À framePosition, modifiez la trajectoire de motionTarget en la déplaçant de percentX ou de percentY selon les coordonnées déterminées par keyPositionType.

Par défaut, MotionLayout arrondit tous les angles introduits en modifiant le tracé. Si vous regardez l'animation que vous venez de créer, vous pouvez voir que la lune suit une trajectoire incurvée au niveau du virage. C'est ce que vous souhaitez pour la plupart des animations. Si ce n'est pas le cas, vous pouvez spécifier l'attribut curveFit pour la personnaliser.

Essayer

Si vous exécutez à nouveau l'application, l'animation de cette étape s'affiche.

46b179c01801f19e.gif

La Lune suit un arc car elle traverse l'KeyPosition spécifié dans l'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 tracé de motionTarget @id/moon en le déplaçant de 50% Y (à mi-chemin vers le bas de l'écran) en fonction des coordonnées déterminées par parentRelative (l'ensemble du MotionLayout)."

Ainsi, à la moitié de l'animation, la lune doit traverser un KeyPosition situé à 50% de l'écran. Ce KeyPosition ne modifie pas du tout le mouvement X. La lune continuera donc d'aller horizontalement du début à la fin. MotionLayout trouvera un trajet fluide qui traverse cette KeyPosition en se déplaçant entre le début et la fin.

Si vous regardez attentivement, le texte des crédits est limité par la position de la lune. Pourquoi ne se déplace-t-elle pas 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 le trajet que suit la Lune, ses positions de début et de fin ne la déplacent pas verticalement. KeyPosition ne modifie pas la position de début ni de fin. Le texte des crédits est donc limité à la position finale de la lune.

Si vous voulez 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 découvrirez les différents types de keyPositionType dans MotionLayout.

6. Comprendre le keyPositionType

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

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

Il existe trois types de keyPosition différents: parentRelative, pathRelative et deltaRelative. La spécification d'un type modifie le système de coordonnées par lequel les valeurs percentX et percentY sont calculées.

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. Ils sont également utiles pour décrire une position à l'écran.

Les systèmes de coordonnées MotionLayout sont un système de coordonnées cartésien. Cela signifie qu'elles ont un axe X et un axe Y défini par deux lignes perpendiculaires. La principale différence 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. Ils autorisent les valeurs négatives et les valeurs supérieures à 1.0. Par exemple, une valeur percentX de -2.0 signifierait d'aller deux fois dans le sens opposé de l'axe X.

Si cela ressemble un peu trop au cours d'algèbre, regardez les images ci-dessous !

parentRelative coordonnées

a7b7568d46d9dec7.png

L'élément keyPositionType de parentRelative utilise le même système de coordonnées que l'écran. Elle définit (0, 0) en haut à gauche de l'ensemble de la 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'intégralité de la MotionLayout, comme l'arc lunaire dans cet exemple.

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

Coordonnées deltaRelative

5680bf553627416c.png

Delta est un terme mathématique qui désigne le changement. deltaRelative est donc une façon de dire "changer la relation relative". Dans deltaRelativecoordonnées, (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 avec l'écran.

L'axe X est toujours horizontal à 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 est déplacée.

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

Coordonnées pathRelative

f3aaadaac8b4a93f.png

Le dernier système de coordonnées de 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) correspond donc à la position de départ et (1,0) à la position de fin.

Pourquoi le choisir ? C'est assez surprenant à première vue, d'autant plus que ce système de coordonnées n'est même pas aligné sur celui de l'écran.

Il s'avère que pathRelative est très utile dans plusieurs cas.

  • Accélérez, ralentissez ou arrêtez une vue pendant une partie de l'animation. Étant donné que la dimension X correspondra toujours exactement au chemin emprunté par la vue, vous pouvez utiliser un KeyPosition pathRelative pour modifier la framePosition atteinte à un point particulier de ce chemin. Ainsi, un KeyPosition à framePosition="50" avec une percentX="0.1" obligerait l'animation à mettre 50% du temps à parcourir les 10 premiers% du mouvement.
  • Ajouter un arc subtil à un tracé Étant donné que la dimension Y est toujours perpendiculaire au mouvement, la modification de Y modifie la trajectoire en courbe par rapport au mouvement global.
  • Ajouter une deuxième dimension lorsque deltaRelative ne fonctionne pas. Pour un mouvement entièrement horizontal et vertical, deltaRelative ne crée qu'une seule dimension utile. Cependant, pathRelative crée toujours des coordonnées X et Y utilisables.

À l'étape suivante, vous allez apprendre à créer des chemins d'accès encore plus complexes en utilisant plusieurs KeyPosition.

7. Construire des chemins complexes

L'animation créée à la dernière étape crée une courbe lisse, mais la forme pourrait ressembler davantage à la lune.

Modifier un tracé avec plusieurs éléments KeyPosition

MotionLayout peut modifier davantage un tracé en définissant autant de KeyPosition que nécessaire pour générer un mouvement. Pour cette animation, vous allez construire un arc, mais vous pouvez faire sauter la lune de haut en bas au milieu de l'écran, si vous le souhaitez.

  1. Ouvrez xml/step4.xml. Vous voyez qu'il possède 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 tracé de @id/moon : un juste avant qu'elle atteigne le sommet et une 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% du chemin d'accès de l'animation, et entraîneront le déplacement de @id/moon sur un tracé situé à 60% du haut de l'écran. Combiné à l'KeyPosition existant à 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 d'animation souhaitée. MotionLayout appliquera chaque KeyPosition à la valeur framePosition spécifiée et déterminera comment créer un mouvement lisse qui traverse toutes les KeyPositions.

Essayer

  1. Exécutez à nouveau l'application. Passez à l'étape 4 pour voir l'animation en action. Lorsque vous cliquez sur la Lune, elle suit son tracé 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 simplement KeyPosition.

Voici un exemple qui montre comment créer un tracé complexe qui se déplace dans les deux sens 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 fini d'explorer KeyPosition, vous allez passer à d'autres types de KeyFrames à l'étape suivante.

8. Modification d'attributs pendant le mouvement

Pour créer des animations dynamiques, vous devez souvent modifier la size, la rotation ou la 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.

Au cours de cette étape, vous allez utiliser KeyAttribute pour définir l'échelle de lune et la faire pivoter. Vous allez également utiliser un KeyAttribute pour retarder l'affichage du texte jusqu'à ce que la Lune ait presque terminé son trajet.

Étape 1: Redimensionnez et faites pivoter avec KeyAttribute

  1. Ouvrez xml/step5.xml, qui contient la même animation que celle créée à la dernière étape. Pour plus de variété, cet écran utilise une image de l'espace différente comme arrière-plan.
  2. Pour agrandir et faire pivoter la lune, 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ées à 50% et 100% de l'animation. La première KeyAttribute à 50% se produira en haut de l'arc, et entraînera le doublement de la taille de la vue et une rotation de -360 degrés (ou un cercle complet). Le deuxième KeyAttribute termine la deuxième rotation à -720 degrés (deux cercles complets) et réduit la taille à la taille normale, car les valeurs scaleX et scaleY sont définies par défaut sur 1,0.

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

KeyAttributes acceptent les attributs qui peuvent être appliqués à toutes les vues. Ils sont compatibles avec la modification des attributs de base tels que visibility, alpha ou elevation. Vous pouvez également modifier la rotation comme vous le faites ici, effectuer une rotation en trois dimensions avec rotateX et rotateY, redimensionner 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 consiste à mettre à jour l'animation afin que le texte des crédits n'apparaisse pas tant que l'animation n'est pas entièrement terminée.

  1. Pour retarder l'affichage des crédits, définissez une autre KeyAttribute qui garantit que alpha restera sur 0 jusqu'à keyPosition="85". MotionLayout effectuera toujours une transition fluide de la version alpha de 0 à 100, mais sur les 15% restants 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 conserve la alpha de @id/credits à 0,0 pour les premiers 85% de l'animation. Puisqu'elle commence à une valeur alpha de 0, elle sera invisible pendant les premiers 85% de l'animation.

L'effet final de cette 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 s'installe dans le coin droit de l'écran.

En décalant les animations d'une vue pendant qu'une autre se déplace ainsi, vous pouvez créer des animations impressionnantes et 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 son trajet du début à la fin, en passant par chaque KeyAttribute spécifié dans les KeyFrameSet.

2f4bfdd681c1fa98.gif

Comme vous faites pivoter la Lune de deux cercles complets, elle effectuera désormais un double retournement, et les crédits retarderont leur affichage jusqu'à ce que l'animation soit presque terminée.

Explorer par vous-même

Avant de passer au type final de KeyFrame, essayez de modifier d'autres attributs standards dans KeyAttributes. Par exemple, essayez de remplacer rotation par rotationX pour voir l'animation générée.

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 des attributs personnalisés

Les animations enrichies impliquent la modification de la couleur ou d'autres attributs d'une vue. Bien que MotionLayout puisse utiliser un KeyAttribute pour modifier n'importe quel attribut standard listé 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 associée à un setter. Par exemple, vous pouvez définir le paramètre 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 un 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 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 dont le type est indiqué dans le nom. Dans cet exemple, la valeur personnalisée est une couleur spécifiée.

Les valeurs personnalisées peuvent avoir l'un des types suivants:

  • Couleur
  • Entier
  • Float
  • Chaîne
  • Dimension
  • Booléen

À l'aide de cette API, MotionLayout peut animer tout ce qui fournit un setter sur n'importe quelle vue.

Essayer

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

5fb6792126a09fda.gif

Lorsque vous ajoutez d'autres KeyFrames, MotionLayout modifie la trajectoire de la Lune en la faisant passer d'une ligne droite à une courbe complexe, ce qui ajoute un double saut périlleux arrière, un redimensionnement et un changement de couleur au milieu de l'animation.

Dans les animations réelles, vous animez souvent plusieurs vues en même temps en contrôlant leur mouvement le long de différents chemins et à différentes vitesses. En spécifiant un KeyFrame différent pour chaque vue, il est possible de créer une chorégraphie d'animations riches qui animent plusieurs vues avec MotionLayout.

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

Au cours de cette étape, vous allez apprendre à utiliser OnSwipe avec des chemins d'accès complexes. Jusqu'à présent, l'animation de la Lune a été déclenchée par un écouteur OnClick et dure une durée fixe.

Pour contrôler des animations dont les tracés sont complexes à l'aide de OnSwipe, comme l'animation lunaire que vous avez créée au cours des dernières étapes, vous devez comprendre le fonctionnement de OnSwipe.

Étape 1: Explorez le comportement d'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. Essayez de produire une animation fluide en faisant glisser la lune le long de la trajectoire de l'arc.

Cette animation n'est pas très satisfaisante lorsque vous l'exécutez. Lorsque la Lune atteint le sommet de l'arc, elle commence à sauter.

ed96e3674854a548.gif

Pour comprendre le bug, réfléchissez à ce qui se passe lorsque l'utilisateur appuie juste en dessous de la partie supérieure de l'arc. Comme la balise OnSwipe comporte un élément motion:touchAnchorSide="bottom", MotionLayout tentera de maintenir la distance entre le doigt et le bas de la vue constant tout au long de l'animation.

Cependant, comme le bas de la Lune ne va pas toujours dans la même direction, il monte, puis revient vers le bas. MotionLayout ne sait donc pas quoi faire lorsque l'utilisateur vient de passer le haut de l'arc. Sachant cela, puisque vous suivez le bas de la Lune, où doit-il être placé lorsque l'utilisateur touche ici ?

56cd575c5c77eddd.png

Étape 2: Utilisez le côté droit

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

Dans cette animation, les côtés right et left de la Lune progresseront tous deux à l'écran dans une direction.

Cependant, bottom et top vont s'inverser. Lorsque OnSwipe tente de le suivre, il est confus quand sa direction change.

  1. Pour que cette animation suive les événements tactiles, définissez touchAnchorSide sur 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 et touchAnchorSide pour modifier la direction d'un rail latéral. Il est toujours important que touchAnchorSide progresse dans une seule direction, mais vous pouvez indiquer à MotionLayout dans quelle direction effectuer le suivi. Par exemple, vous pouvez conserver touchAnchorSide="bottom", mais ajouter dragDirection="dragRight". MotionLayout suivra la position du bas de la vue, mais ne tiendra compte de sa position que lorsqu'il se déplacera vers la droite (les mouvements verticaux seront ignorés). Ainsi, même si la partie inférieure monte et descend, l'animation s'effectue toujours 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 le long du tracé. Même s'il suit un arc complexe, MotionLayout peut faire progresser l'animation en réponse aux événements de balayage.

5458dff382261427.gif

11. Mouvement en cours avec du code

MotionLayout permet de créer des animations enrichies avec 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 constatez qu'un CoordinatorLayout et un AppBarLayout fonctionnels sont déjà créé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 des 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 qui peut être réduite comme celle-ci sur Android : le défilement du texte sera "coordonné". avec l'en-tête escamotable.

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

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

Étape 2: Faire défiler MotionLayout

  1. Pour faire défiler la vue MotionLayout 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 constatez 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 indiquer à MotionLayout les changements de 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 fait défiler la page 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 valeur 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. 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 pour la barre d'outils de réduction dynamique à l'aide de MotionLayout. En utilisant une séquence de KeyFrames, vous pouvez obtenir des effets très audacieux.

12. Félicitations

Cet atelier de programmation a couvert l'API de base de MotionLayout.

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

En savoir plus

MotionLayout prend en charge 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étés, et KeyTimeCycle,, qui vous permet d'animer des événements en fonction de l'heure. Consultez les exemples pour chaque méthode.

Pour obtenir des liens vers d'autres ateliers de ce cours, consultez la page de destination des ateliers de programmation avancés pour Android en Kotlin.