Points Clés
Timeline est un modèle temporel d'animation qui définit quand une animation commence, combien de temps elle dure et comment la progression du temps est transformée en modifications du paramètre animé. Contrairement à l'animation image par image (frame-by-frame), où chaque image est définie manuellement, l'animation par chronologie est décrite par des paramètres : duration — durée totale, startDelay — délai avant le début, repeatCount — nombre de répétitions, et timingFunction — la courbe de vitesse.
La chronologie opère dans un espace normalisé [0, 1] — temps local ou progression. La valeur zéro correspond au début de l'animation, un à la fin. Une fonction d'easing (lissage) convertit le temps local linéaire en progression réelle de l'animation. Si repeatCount > 1, la chronologie cycle (RESTART) ou s’inverse (REVERSE). Sous iOS, CAMediaTiming ajoute speed (vitesse de lecture, >1 accélère) et timeOffset (décalage de la chronologie). Sous Android, Animator utilise setCurrentPlayTime() pour le contrôle direct de la position sur la chronologie. Selon Material Design, les animations de moins de 100 ms ne sont pas perçues comme des animations, tandis que celles de plus de 500 ms commencent à irriter. La durée optimale pour les animations d'interface est de 200 à 350 ms.
Sous iOS, la gestion de la chronologie d'animation est effectuée via le protocole CAMediaTiming, implémenté par CAAnimation et CALayer. Il fournit les propriétés : duration (en secondes), beginTime (heure de début relative à la chronologie parente), repeatCount (nombre de répétitions), repeatDuration (durée totale des répétitions), autoreverses (inversion automatique), fillMode (comportement avant/après l'animation), speed (vitesse de lecture) et timeOffset (décalage de la chronologie).
CACurrentMediaTime() est une fonction qui renvoie le temps absolu en secondes depuis le dernier redémarrage de l'appareil. Elle est utilisée comme horodatage de base pour synchroniser plusieurs animations : définissez beginTime = CACurrentMediaTime() + offset pour lancer un groupe d'animations avec un délai précis. CAMediaTiming prend également en charge la hiérarchie des chronologies — une couche parent peut accélérer/ralentir toutes les animations enfants via la propriété speed. Dans UIKit, UIViewPropertyAnimator (iOS 10+) abstrait la chronologie via les propriétés duration, delay et les callbacks startAnimation/pauseAnimation/stopAnimation. Dans SwiftUI, la structure Animation fournit duration dans le cadre de la spécification de l'animation, mais le contrôle direct de la chronologie est masqué — pour une chronologie personnalisée, utilisez TimelineView (iOS 15+) avec un scheduler personnalisé.
Sous Android, la gestion de la chronologie est centralisée dans la classe Animator et ses sous-classes (ValueAnimator, ObjectAnimator). Les principales propriétés de la chronologie sont : duration (en millisecondes), startDelay (délai avant le début), repeatCount (nombre de répétitions), repeatMode (RESTART ou REVERSE). La méthode setCurrentPlayTime(long) permet de naviguer sur la chronologie à n’importe quelle position arbitraire.
ValueAnimator ajoute une couche d'abstraction : il génère des valeurs de 0 à 1 (temps normalisé) basées sur duration et les transmet à TimeInterpolator. AnimatorSet permet de combiner plusieurs Animator en une seule chronologie avec une exécution séquentielle (playSequentially) ou parallèle (playTogether). Dans Jetpack Compose, la chronologie est gérée via AnimationSpec : tween (durationMillis, delayMillis, easing), spring (dampingRatio, stiffness) et keyframes (points sur la chronologie avec easing personnalisé pour chaque segment). Compose AnimatedContent utilise une chronologie pour transitionSpec — le développeur décrit comment le contenu apparaît/disparaît avec des décalages temporels. Selon Android Performance, la chronologie d'animation doit être un multiple de 16 ms (cycle vsync) pour 60 ips — une durée incorrecte peut entraîner des chutes d'images en raison d'une inadéquation avec la fréquence de rafraîchissement de l'écran.
La chronologie devient particulièrement importante lors de la composition de plusieurs animations. Trois modèles de base : cascade — les animations démarrent séquentiellement avec un décalage (chaque suivante commence après un offset de la précédente), parallèle — toutes les animations commencent simultanément avec la même chronologie, et stagger — les animations démarrent avec chevauchement (la seconde commence avant la fin de la première).
Sous iOS, la cascade est implémentée via beginTime de chaque CAAnimation = beginTime précédent + duration précédente. Pour le stagger, CAAnimationGroup est utilisé avec différentes valeurs de beginTime dans les animations imbriquées. Sous Android, AnimatorSet prend en charge playSequentially et playTogether ; le stagger est créé en définissant startDelay pour chaque Animator. Dans Compose, AnimatedVisibility avec enter/exitTransition utilise le stagger dans la variante staggerChildren pour l'apparition en cascade des éléments de liste. À la WWDC et Google I/O 2024, les deux plateformes ont présenté des outils de visualisation de chronologie : Xcode Animations Inspector et Android Studio Animations Timeline — ils montrent la chronologie réelle et les images perdues en temps réel.
Un exemple d'animation avec une chronologie personnalisée : délai de 0,3 s, durée de 0,6 s, inversion automatique et répétition infinie. CAMediaTiming contrôle tous les paramètres temporels.
import UIKit
func breathingAnimation(layer: CALayer) {
let animation = CABasicAnimation(keyPath: "opacity")
animation.duration = 0.6
animation.beginTime = CACurrentMediaTime() + 0.3
animation.fromValue = 1.0
animation.toValue = 0.3
animation.autoreverses = true
animation.repeatCount = .infinity
animation.speed = 1.0
animation.fillMode = .both
layer.add(animation, forKey: "breathing")
}
beginTime = CACurrentMediaTime() + 0,3 fournit un délai de 300 ms. autoreverses = true fait s’exécuter l’animation en sens inverse après chaque cycle. repeatCount = .infinity crée une boucle infinie de l’effet de « respiration ». speed = 1,0 — vitesse normale ; accélérez l’animation 2x avec une valeur de 2,0.
SwiftUI TimelineView (iOS 15+) fournit un accès à la chronologie système pour une animation personnalisée. La vue se met à jour à chaque tick du scheduler.
import SwiftUI
struct TimelineViewExample: View {
var body: some View {
TimelineView(.periodic(from: .now, by: 0.1)) { context in
let seconds = context.date.timeIntervalSinceReferenceDate
let phase = sin(seconds * 2 * .pi)
Circle()
.fill(Color.blue)
.frame(width: 60 + 30 * phase)
.animation(.linear(duration: 0), value: phase)
}
}
}
TimelineView avec le scheduler .periodic génère des ticks toutes les 0,1 s. Dans la fermeture, context.date est l’heure actuelle, qui est convertie en une phase d’onde sinusoïdale. La taille du cercle pulse à une fréquence de 1 Hz. TimelineView est le seul moyen de créer une animation avec sa propre chronologie dans SwiftUI sans l’API Animation.
ValueAnimator avec contrôle complet de la chronologie : délai, durée, répétition. La méthode setCurrentPlayTime permet de sauter le long de la chronologie.
import android.animation.ValueAnimator
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.View
fun View.timelineAnimation() {
ValueAnimator.ofFloat(0f, 1f).apply {
duration = 400
startDelay = 200
repeatCount = 2
repeatMode = ValueAnimator.REVERSE
interpolator = AccelerateDecelerateInterpolator()
addUpdateListener { animator ->
val progress = animator.animatedFraction
alpha = progress
translationX = progress * 100f
}
start()
}
}
startDelay = 200 ms — délai avant le début. repeatMode = REVERSE ramène l’animation à la valeur initiale. animatedFraction donne la progression de 0 à 1 en tenant compte de repeatMode. addUpdateListener se déclenche à chaque image (généralement 16 ms). Utilisez pause() / resume() pour mettre en pause.
AnimatorSet permet de créer des chronologies complexes à partir de plusieurs animations. L’exemple illustre une exécution séquentielle avec chevauchement (stagger).
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.view.View
fun View.staggerAnimation() {
val fadeIn = ObjectAnimator.ofFloat(this, "alpha", 0f, 1f).apply {
duration = 300
}
val slideUp = ObjectAnimator.ofFloat(this, "translationY", 100f, 0f).apply {
duration = 400
startDelay = 100
}
val scale = ObjectAnimator.ofFloat(this, "scaleX", 0.8f, 1f).apply {
duration = 250
startDelay = 200
}
AnimatorSet().apply {
playTogether(fadeIn, slideUp, scale)
start()
}
}
playTogether lance toutes les animations en parallèle, mais chacune a son propre startDelay — cela crée un effet de stagger : fadeIn commence en premier (0 ms), slideUp après 100 ms, scale après 200 ms. La chronologie résultante : 0 — 100 — 200 — 600 ms. Pour une exécution séquentielle, utilisez playSequentially.
Questions fréquentes
Animator est une classe de base abstraite pour toutes les animations. ValueAnimator est sa sous-classe qui anime des valeurs numériques sans liaison à un objet. ObjectAnimator (une sous-classe de ValueAnimator) anime une propriété spécifique d’un objet (par exemple, alpha ou translationY). Pour la chronologie, utilisez ValueAnimator comme minuteur universel avec un callback via addUpdateListener.
Utilisez les horloges système (CACurrentMediaTime sous iOS, System.nanoTime sous Android) pour calculer la progression, pas le nombre d’images. Pour la synchronisation réseau, utilisez une minuterie basée sur NTP avec correction de décalage. iOS prend en charge AVAudioSession pour la synchronisation de la chronologie audio-vidéo avec une précision milliseconde. Évitez la synchronisation basée sur les images — les fréquences d’images varient de 30 à 120 ips.
startOffset est le délai avant le début de l’animation en millisecondes. Utilisé pour les effets de cascade et de stagger. repeatCount est le nombre de répétitions : 0 = pas de répétition, ValueAnimator.INFINITE = boucle infinie. repeatMode détermine la direction : RESTART (réinitialisation au début) ou REVERSE (direction inverse). Sous iOS, startOffset est appelé beginTime (en combinaison avec CACurrentMediaTime), repeatCount est la même propriété de CABasicAnimation.
Material Design recommande 200 à 350 ms pour les animations d’interface. Les animations de moins de 100 ms ne sont pas perçues comme des animations, tandis que celles de plus de 500 ms sont irritantes. Pour l’entrée/sortie des éléments : 200 à 300 ms. Pour les transitions entre écrans : 300 à 400 ms. Utilisez des durées plus courtes pour les petits éléments (100 à 200 ms) et plus longues pour les grands éléments (400 à 500 ms).
Le tremblement (jank) se produit en raison : d’opérations longues sur le thread principal (bloquant le rendu), d’une hiérarchie complexe de View/Layer avec overdraw, ou d’une utilisation incorrecte de la chronologie (durée non multiple de vsync — 16 ms pour 60 ips, 8 ms pour 120 ips). Utilisez Xcode Instruments Animations ou Android Studio Profiler pour détecter les images perdues. Dans Compose, utilisez Modifier.drawWithCache pour mettre en cache les dessins complexes.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi