Animer le mouvement à l'aide d'une animation à effet ressort

Essayer Compose
Jetpack Compose est le kit d'outils d'interface utilisateur recommandé pour Android. Découvrez comment utiliser des animations dans Compose.

Le mouvement basé sur la physique est stimulé par la force. La force de ressort est l'une de ces forces qui guident l'interactivité et le mouvement. Une force de ressort présente les propriétés suivantes : amortissement et raideur. Dans une animation basée sur le ressort, la valeur et la vitesse sont calculées en fonction de la force de tension appliquée à chaque image.

Si vous souhaitez que les animations de votre application ralentissent dans une seule direction, utilisez plutôt une animation de déplacement basée sur des frictions.

Cycle de vie d'une animation de rétroaction

Dans une animation basée sur les ressorts, la classe SpringForce vous permet de personnaliser la raideur du ressort, son taux d'amortissement et sa position finale. Dès le début de l'animation, la force de ressort met à jour la valeur de l'animation et la vitesse de chaque image. L'animation se poursuit jusqu'à ce que la force de tension atteigne un équilibre.

Par exemple, si vous faites glisser une icône d'application sur l'écran, puis relâchez-la en levant le doigt dessus, celle-ci revient à son emplacement d'origine grâce à une force invisible mais familière.

La figure 1 illustre un effet de ressort similaire. Le signe plus (+) au milieu du cercle indique la force appliquée par un geste tactile.

Version de printemps
Figure 1. Effet de rétroaction

Créer une animation de rétroaction

Pour créer une animation de rétroaction pour votre application, procédez comme suit:

Les sections suivantes décrivent en détail les étapes générales de création d'une animation de rétroaction.

Ajouter la bibliothèque Support

Pour utiliser la bibliothèque Support basée sur la physique, vous devez l'ajouter à votre projet comme suit:

  1. Ouvrez le fichier build.gradle du module de votre application.
  2. Ajoutez la bibliothèque Support à la section dependencies.

    Groovy

            dependencies {
                def dynamicanimation_version = '1.0.0'
                implementation "androidx.dynamicanimation:dynamicanimation:$dynamicanimation_version"
            }
            

    Kotlin

            dependencies {
                val dynamicanimation_version = "1.0.0"
                implementation("androidx.dynamicanimation:dynamicanimation:$dynamicanimation_version")
            }
            

    Pour afficher les versions actuelles de cette bibliothèque, consultez les informations concernant DynamicAnimation sur la page des versions.

Créer une animation de rétroaction

La classe SpringAnimation vous permet de créer une animation de rétroaction pour un objet. Pour créer une animation de rétroaction, vous devez créer une instance de la classe SpringAnimation et fournir un objet, la propriété d'un objet que vous souhaitez animer et, si vous le souhaitez, une position de ressort finale à l'endroit où vous souhaitez que l'animation repose.

Remarque:Au moment de la création d'une animation de rétroaction, la position finale du ressort est facultative. Cependant, il doit être défini avant de lancer l'animation.

Kotlin

val springAnim = findViewById<View>(R.id.imageView).let { img ->
    // Setting up a spring animation to animate the view’s translationY property with the final
    // spring position at 0.
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y, 0f)
}

Java

final View img = findViewById(R.id.imageView);
// Setting up a spring animation to animate the view’s translationY property with the final
// spring position at 0.
final SpringAnimation springAnim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y, 0);

L'animation basée sur les ressorts peut animer des vues à l'écran en modifiant les propriétés réelles des objets de vue. Les vues suivantes sont disponibles dans le système:

  • ALPHA : représente la transparence alpha sur la vue. La valeur est 1 (opaque) par défaut, et la valeur 0 correspond à une transparence totale (non visible).
  • TRANSLATION_X, TRANSLATION_Y et TRANSLATION_Z: ces propriétés contrôlent l'emplacement de la vue sous la forme d'un delta par rapport à sa coordonnée gauche, sa coordonnée supérieure et son altitude, qui sont définies par son conteneur de mise en page.
  • ROTATION, ROTATION_X et ROTATION_Y: ces propriétés contrôlent la rotation en 2D (propriété rotation) et en 3D autour du point pivot.
  • SCROLL_X et SCROLL_Y: ces propriétés indiquent le décalage de défilement de la source, vers la gauche et le bord supérieur, en pixels. Elle indique également la position en termes de défilement de la page.
  • SCALE_X et SCALE_Y: ces propriétés contrôlent la mise à l'échelle 2D d'une vue autour de son point pivot.
  • X, Y et Z: ces propriétés utilitaires de base décrivent l'emplacement final de la vue dans son conteneur.

Enregistrer des écouteurs

La classe DynamicAnimation fournit deux écouteurs: OnAnimationUpdateListener et OnAnimationEndListener. Ces écouteurs écoutent les mises à jour de l'animation, par exemple en cas de changement de la valeur de l'animation et lorsque celle-ci se termine.

OnAnimationUpdateListener

Lorsque vous souhaitez animer plusieurs vues pour créer une animation en chaîne, vous pouvez configurer OnAnimationUpdateListener pour qu'il reçoive un rappel chaque fois que la propriété de la vue actuelle est modifiée. Le rappel informe l'autre vue qu'elle doit mettre à jour sa position de ressort en fonction du changement apporté à la propriété de la vue actuelle. Pour enregistrer l'écouteur, procédez comme suit:

  1. Appelez la méthode addUpdateListener() et associez l'écouteur à l'animation.

    Remarque:Vous devez enregistrer l'écouteur de mise à jour avant le début de l'animation. Toutefois, l'écouteur de mise à jour ne doit être enregistré que si vous avez besoin d'une mise à jour par image en cas de modification de la valeur de l'animation. Un écouteur de mise à jour empêche l'exécution potentielle de l'animation sur un thread distinct.

  2. Ignorez la méthode onAnimationUpdate() pour informer l'appelant de la modification de l'objet actuel. L'exemple de code suivant illustre l'utilisation globale de OnAnimationUpdateListener.

Kotlin

// Setting up a spring animation to animate the view1 and view2 translationX and translationY properties
val (anim1X, anim1Y) = findViewById<View>(R.id.view1).let { view1 ->
    SpringAnimation(view1, DynamicAnimation.TRANSLATION_X) to
            SpringAnimation(view1, DynamicAnimation.TRANSLATION_Y)
}
val (anim2X, anim2Y) = findViewById<View>(R.id.view2).let { view2 ->
    SpringAnimation(view2, DynamicAnimation.TRANSLATION_X) to
            SpringAnimation(view2, DynamicAnimation.TRANSLATION_Y)
}

// Registering the update listener
anim1X.addUpdateListener { _, value, _ ->
    // Overriding the method to notify view2 about the change in the view1’s property.
    anim2X.animateToFinalPosition(value)
}

anim1Y.addUpdateListener { _, value, _ -> anim2Y.animateToFinalPosition(value) }

Java

// Creating two views to demonstrate the registration of the update listener.
final View view1 = findViewById(R.id.view1);
final View view2 = findViewById(R.id.view2);

// Setting up a spring animation to animate the view1 and view2 translationX and translationY properties
final SpringAnimation anim1X = new SpringAnimation(view1,
        DynamicAnimation.TRANSLATION_X);
final SpringAnimation anim1Y = new SpringAnimation(view1,
    DynamicAnimation.TRANSLATION_Y);
final SpringAnimation anim2X = new SpringAnimation(view2,
        DynamicAnimation.TRANSLATION_X);
final SpringAnimation anim2Y = new SpringAnimation(view2,
        DynamicAnimation.TRANSLATION_Y);

// Registering the update listener
anim1X.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {

// Overriding the method to notify view2 about the change in the view1’s property.
    @Override
    public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float value,
                                  float velocity) {
        anim2X.animateToFinalPosition(value);
    }
});

anim1Y.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {

  @Override
    public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float value,
                                  float velocity) {
        anim2Y.animateToFinalPosition(value);
    }
});

OnAnimationEndListener

OnAnimationEndListener notifie la fin d'une animation. Vous pouvez configurer l'écouteur pour recevoir un rappel chaque fois que l'animation atteint l'équilibre ou qu'elle est annulée. Pour enregistrer l'écouteur, procédez comme suit:

  1. Appelez la méthode addEndListener() et associez l'écouteur à l'animation.
  2. Ignorez la méthode onAnimationEnd() pour recevoir une notification chaque fois qu'une animation atteint l'équilibre ou est annulée.

Supprimer les écouteurs

Pour arrêter de recevoir des rappels de mise à jour de l'animation et des rappels de fin d'animation, appelez respectivement les méthodes removeUpdateListener() et removeEndListener().

Définir la valeur de début de l'animation

Pour définir la valeur de début de l'animation, appelez la méthode setStartValue() et transmettez la valeur de début de l'animation. Si vous ne définissez pas la valeur de début, l'animation utilise la valeur actuelle de la propriété de l'objet comme valeur de début.

Définir la plage de valeurs de l'animation

Vous pouvez définir les valeurs minimale et maximale de l'animation lorsque vous souhaitez restreindre la valeur de la propriété à une certaine plage. Il est également utile de contrôler la plage si vous animez des propriétés ayant une plage intrinsèque, comme alpha (de 0 à 1).

  • Pour définir la valeur minimale, appelez la méthode setMinValue() et transmettez la valeur minimale de la propriété.
  • Pour définir la valeur maximale, appelez la méthode setMaxValue() et transmettez la valeur maximale de la propriété.

Les deux méthodes renvoient l'animation pour laquelle la valeur est définie.

Remarque:Si vous avez défini la valeur de début et défini une plage de valeurs pour l'animation, assurez-vous que la valeur de début se situe entre les valeurs minimale et maximale.

Définir la vitesse de départ

La vitesse de début définit la vitesse à laquelle la propriété de l'animation change au début de l'animation. La vitesse de démarrage par défaut est définie sur zéro pixel par seconde. Vous pouvez définir la vitesse avec la vitesse des gestes tactiles ou en utilisant une valeur fixe comme vitesse de départ. Si vous choisissez de fournir une valeur fixe, nous vous recommandons de la définir en dp par seconde, puis de la convertir en pixels par seconde. En définissant la valeur en dp par seconde, la vitesse peut être indépendante de la densité et des facteurs de forme. Pour en savoir plus sur la conversion d'une valeur en pixels par seconde, consultez la section Convertir des dp par seconde en pixels par seconde.

Pour définir la vitesse, appelez la méthode setStartVelocity() et transmettez la vitesse en pixels par seconde. La méthode renvoie l'objet de force de tension sur lequel la vitesse est définie.

Remarque:Utilisez les méthodes de classe GestureDetector.OnGestureListener ou VelocityTracker pour récupérer et calculer la vitesse des gestes tactiles.

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Compute velocity in the unit pixel/second
        vt.computeCurrentVelocity(1000)
        val velocity = vt.yVelocity
        setStartVelocity(velocity)
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Compute velocity in the unit pixel/second
vt.computeCurrentVelocity(1000);
float velocity = vt.getYVelocity();
anim.setStartVelocity(velocity);

Conversion de dp par seconde en pixels par seconde

La vélocité d'un ressort doit être exprimée en pixels par seconde. Si vous choisissez de fournir une valeur fixe comme point de départ de la vitesse, indiquez la valeur en dp par seconde, puis convertissez-la en pixels par seconde. Pour la conversion, utilisez la méthode applyDimension() de la classe TypedValue. Consultez l'exemple de code suivant:

Kotlin

val pixelPerSecond: Float =
    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, resources.displayMetrics)

Java

float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, getResources().getDisplayMetrics());

Définir les propriétés du ressort

La classe SpringForce définit les méthodes "getter" et "setter" pour chacune des propriétés de ressort, telles que le taux d'amortissement et la raideur. Pour définir les propriétés du ressort, il est important de récupérer l'objet de force de ressort ou de créer une force de ressort personnalisée sur laquelle vous pouvez définir les propriétés. Pour plus d'informations sur la création d'une force de ressort personnalisée, consultez la section Créer une force de ressort personnalisée.

Conseil:Lorsque vous utilisez les méthodes setter, vous pouvez créer une chaîne de méthodes, car toutes les méthodes setter renvoient l'objet de force de tension.

Taux d'amortissement

Le taux d'amortissement décrit une réduction progressive de l'oscillation du ressort. En utilisant le taux d'amortissement, vous pouvez définir la vitesse à laquelle les oscillations diminuent d'un rebond à l'autre. Vous pouvez amortir un ressort de quatre manières différentes:

  • Le suramortissement se produit lorsque le taux d'amortissement est supérieur à un. Cela permet à l'objet de se remettre doucement en position au repos.
  • L'amortissement critique se produit lorsque le taux d'amortissement est égal à un. Elle permet à l'objet de revenir à sa position au repos dans le délai le plus court.
  • Le sous-amortissement se produit lorsque le taux d'amortissement est inférieur à un. Elle permet à l'objet de dépasser plusieurs fois en transmettant la position de repos, puis d'atteindre progressivement cette position.
  • Elle se produit lorsque le taux d'amortissement est égal à zéro. Elle permet à l'objet d'osciller indéfiniment.

Pour ajouter le taux d'amortissement au ressort, procédez comme suit:

  1. Appelez la méthode getSpring() pour récupérer le ressort afin d'ajouter le taux d'amortissement.
  2. Appelez la méthode setDampingRatio() et transmettez le taux d'amortissement que vous souhaitez ajouter au ressort. Cette méthode renvoie l'objet de force de ressort sur lequel le taux d'amortissement est défini.

    Remarque:Le taux d'amortissement doit être un nombre non négatif. Si vous définissez le taux d'amortissement sur zéro, le ressort n'atteindra jamais sa position au repos. En d'autres termes, il oscille indéfiniment.

Les constantes de taux d'amortissement suivantes sont disponibles dans le système:

Figure 2: Rebond élevé

Figure 3: Rebond moyen

Figure 4: Rebond faible

Figure 5: Aucun rebond

Le taux d'amortissement par défaut est défini sur DAMPING_RATIO_MEDIUM_BOUNCY.

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Setting the damping ratio to create a low bouncing effect.
        spring.dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY
        …
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Setting the damping ratio to create a low bouncing effect.
anim.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
…

Raideur

La raideur définit la constante du ressort, qui mesure la force du ressort. Un ressort rigide applique une plus grande force à l'objet fixé lorsque le ressort n'est pas en position au repos. Pour renforcer la raideur du ressort, procédez comme suit:

  1. Appelez la méthode getSpring() pour récupérer le ressort afin d'ajouter la raideur.
  2. Appelez la méthode setStiffness() et transmettez la valeur de raideur que vous souhaitez ajouter au ressort. Cette méthode renvoie l'objet de force de ressort sur lequel la raideur est définie.

    Remarque:La raideur doit être un nombre positif.

Les constantes de raideur suivantes sont disponibles dans le système:

Figure 6: Raideur élevée

Figure 7: Raideur moyenne

Figure 8: Faible raideur

Figure 9: Raideur très faible

La raideur par défaut est définie sur STIFFNESS_MEDIUM.

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Setting the spring with a low stiffness.
        spring.stiffness = SpringForce.STIFFNESS_LOW
        …
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Setting the spring with a low stiffness.
anim.getSpring().setStiffness(SpringForce.STIFFNESS_LOW);
…

Créer une force de ressort personnalisée

Vous pouvez créer une force de ressort personnalisée au lieu d'utiliser la force de ressort par défaut. La force de ressort personnalisée vous permet de partager la même instance de force de ressort entre plusieurs animations de ressort. Une fois que vous avez créé la force de ressort, vous pouvez définir des propriétés telles que le taux d'amortissement et la raideur.

  1. Créer un objet SpringForce.

    SpringForce force = new SpringForce();

  2. Attribuez les propriétés en appelant les méthodes correspondantes. Vous pouvez également créer une chaîne de méthodes.

    force.setDampingRatio(DAMPING_RATIO_LOW_BOUNCY).setStiffness(STIFFNESS_LOW);

  3. Appelez la méthode setSpring() pour définir le ressort sur l'animation.

    setSpring(force);

Démarrer l'animation

Vous pouvez démarrer une animation de rétroaction de deux manières: en appelant start() ou en appelant la méthode animateToFinalPosition(). Les deux méthodes doivent être appelées sur le thread principal.

animateToFinalPosition() effectue deux tâches:

  • Définit la position finale du ressort.
  • Lance l'animation, si ce n'est pas déjà fait.

Étant donné qu'elle met à jour la position finale du ressort et lance l'animation si nécessaire, vous pouvez l'appeler à tout moment pour modifier le cours d'une animation. Par exemple, dans une animation de rétroaction en chaîne, l'animation d'une vue dépend d'une autre vue. Pour une telle animation, il est plus pratique d'utiliser la méthode animateToFinalPosition(). En utilisant cette méthode dans une animation de rétroaction en chaîne, vous n'avez pas à vous soucier si l'animation que vous souhaitez mettre à jour est en cours d'exécution.

La figure 10 illustre une animation de rétroaction en chaîne, où l'animation d'une vue dépend d'une autre vue.

Démonstration des ressorts en chaîne
Figure 10 : Démonstration des ressorts en chaîne

Pour utiliser la méthode animateToFinalPosition(), appelez la méthode animateToFinalPosition() et transmettez la position reste du ressort. Vous pouvez également définir la position de repos du ressort en appelant la méthode setFinalPosition().

La méthode start() ne définit pas immédiatement la valeur de la propriété sur la valeur de début. La valeur de la propriété change à chaque impulsion de l'animation, ce qui se produit avant la fin du dessin. Par conséquent, les modifications sont reflétées dans le frame suivant, comme si les valeurs étaient définies immédiatement.

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Starting the animation
        start()
        …
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Starting the animation
anim.start();
…

Annuler l'animation

Vous pouvez annuler l'animation ou passer à la fin. Idéalement, vous devez annuler l'ami ou passer directement à la fin lorsqu'une interaction de l'utilisateur exige l'arrêt immédiat de l'animation. Cela se produit principalement lorsqu'un utilisateur quitte brusquement une application ou que la vue devient invisible.

Vous pouvez utiliser deux méthodes pour arrêter l'animation. La méthode cancel() met fin à l'animation sur la valeur où elle se trouve. La méthode skipToEnd() ignore l'animation jusqu'à la valeur finale, puis l'arrête.

Avant de pouvoir arrêter l'animation, il est important de vérifier l'état du ressort. Si l'état n'est pas amorti, l'animation ne pourra jamais atteindre la position repos. Pour vérifier l'état du ressort, appelez la méthode canSkipToEnd(). Si le ressort est amorti, la méthode renvoie true. Sinon, elle renvoie false.

Une fois que vous connaissez l'état du ressort, vous pouvez arrêter une animation à l'aide de la méthode skipToEnd() ou cancel(). La méthode cancel() doit être appelée uniquement sur le thread principal.

Remarque:En général, la méthode skipToEnd() provoque un saut visuel.