Points clés
L'Easing (fonction de lissage) est une fonction mathématique f(t) qui mappe le temps linéaire t ∈ [0,1] à une valeur d'animation non linéaire. Si une animation change la position d'un objet de A à B en 500 ms, l'easing détermine comment l'objet se déplace entre ces points à chaque instant. L'easing linéaire (f(t) = t) signifie une vitesse constante — l'objet se déplace mécaniquement. L'easing non linéaire semble naturel : l'objet accélère (ease-in), décélère (ease-out) ou a un début et une fin fluides (ease-in-out).
Les fonctions d'easing sont décrites par des courbes de Bézier cubiques, où l'axe X est le temps normalisé et l'axe Y est la progression normalisée. Deux points de contrôle (P1, P2) définissent la forme de la courbe. Les courbes standard sont fixées par la spécification CSS : ease (0.25, 0.1, 0.25, 1.0), ease-in (0.42, 0.0, 1.0, 1.0), ease-out (0.0, 0.0, 0.58, 1.0), ease-in-out (0.42, 0.0, 0.58, 1.0). Ces mêmes valeurs sont utilisées dans CAMediaTimingFunction sous iOS et PathInterpolator sous Android. Selon les Directives de Mouvement de Material Design, les courbes d'easing doivent correspondre au contexte : pour l'apparition — ease-out (départ rapide), pour la disparition — ease-in (fin rapide).
Sous iOS, l'easing fonctionne principalement via la classe CAMediaTimingFunction de Core Animation. Elle accepte une courbe de Bézier cubique comme deux points de contrôle (c1x, c1y, c2x, c2y) et s'applique à CAAnimation (CABasicAnimation, CAKeyframeAnimation). CAMediaTimingFunction prend en charge 5 fonctions prédéfinies : linear, easeIn, easeOut, easeInEaseOut et default (équivalent à easeInEaseOut).
CAMediaTimingFunction fonctionne au niveau CALayer — l'animation s'exécute dans un processus serveur de rendu séparé, garantissant 60fps même avec des transformations complexes. Pour l'animation des propriétés UIView (frame, alpha, transform), UIKit utilise UIView.animate avec le paramètre options : .curveEaseInOut, qui crée également en interne une CAMediaTimingFunction avec la courbe correspondante. Sous iOS 17+, UIViewAnimationCurve.custom avec BlockParameters pour Bézier personnalisée a été ajouté — cela élimine le besoin d'écrire une CAAnimation personnalisée. CAMediaTimingFunction est également utilisée dans UIKit Dynamics et UIScrollView pour la décélération (UIScrollViewDecelerationRate).
Dans SwiftUI, l'easing est défini via la structure Animation avec les modificateurs .easeIn, .easeOut, .easeInOut, .spring et .interpolatingSpring. Contrairement à UIKit, où l'animation est procédurale (beginAnimations/commitAnimations), SwiftUI utilise une approche déclarative : l'animation est une propriété d'état qui change, et le framework détermine lui-même comment animer la transition.
Le modificateur .timingCurve(c0x, c0y, c1x, c1y) accepte les points de contrôle Bézier cubique directement — c'est un analogue complet de CAMediaTimingFunction. SwiftUI Animation prend en charge les courbes prédéfinies comme propriétés statiques : Animation.easeInOut est équivalent à ease-in-out (0.42, 0.0, 0.58, 1.0). Pour l'animation spring, Animation.spring(response:dampingFraction:blendDuration) est utilisée, où dampingFraction < 1 crée un effet de ressort avec amortissement. À la WWDC 2023, Apple a présenté Animation.keyframes — une animation image par image avec un easing personnalisé sur chaque image clé, offrant un contrôle total sur la trajectoire.
Sous Android, l'easing est implémenté via PathInterpolator (API 21+) — une classe qui accepte un objet Path avec une courbe Bézier cubique. Les points de contrôle sont spécifiés via Path.cubicTo(x1, y1, x2, y2, x3, y3), où (x1,y1) est le premier point de contrôle, (x2,y2) le second, et (x3,y3) le point final (toujours 1,1). PathInterpolator est obligatoire si une courbe personnalisée est nécessaire ; pour les scénarios standard, AccelerateDecelerateInterpolator (ease-in-out), AccelerateInterpolator (ease-in) et DecelerateInterpolator (ease-out) sont disponibles.
Dans Jetpack Compose, l'easing est défini via l'objet Easing du paquet androidx.compose.animation. FastOutSlowInEasing, LinearOutSlowInEasing, FastOutLinearInEasing sont disponibles, ainsi qu'un Easing personnalisé via CubicBezierEasing. L'Easing Compose est appliqué dans animate*AsState, AnimatedVisibility et AnimatedContent. La différence clé par rapport au système View : Compose interpole l'easing côté Kotlin (pas dans RenderNode), ce qui offre plus de flexibilité pour les animations personnalisées mais nécessite une optimisation sur les écrans 120Hz.
Animation de base de position de couche avec une courbe ease-in-out personnalisée. CAMediaTimingFunction accepte les points de contrôle Bézier cubique qui définissent un début et une fin fluides du mouvement.
import UIKit
func animateLayerPosition() {
let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = CGPoint(x: 0, y: 0)
animation.toValue = CGPoint(x: 300, y: 400)
animation.duration = 0.8
let easing = CAMediaTimingFunction(
controlPoints: 0.42, 0.0, 0.58, 1.0
)
animation.timingFunction = easing
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
layer.add(animation, forKey: "positionAnimation")
}
Les points de contrôle (0.42, 0.0, 0.58, 1.0) définissent la courbe standard ease-in-out. SwiftUI utilise les mêmes points pour Animation.easeInOut. Pour ease-in, utilisez (0.42, 0.0, 1.0, 1.0) ; pour ease-out — (0.0, 0.0, 0.58, 1.0). Core Animation exécute l'animation dans le serveur de rendu à 60/120 fps.
Animation déclarative dans SwiftUI avec une courbe personnalisée et un effet spring. Le modificateur d'animation se lie au changement de isAnimated.
import SwiftUI
struct EasingExampleView: View {
@State private var isAnimated = false
var body: some View {
VStack {
Circle()
.fill(Color.blue)
.frame(width: 60, height: 60)
.offset(x: isAnimated ? 100 : 0, y: 0)
.animation(
.timingCurve(0.42, 0.0, 0.58, 1.0, duration: 0.6),
value: isAnimated
)
Button("Toggle") {
isAnimated.toggle()
}
}
}
}
Le modificateur .timingCurve(c0x, c0y, c1x, c1y, duration) crée une animation d'easing personnalisée dans SwiftUI. Le paramètre value lie l'animation à un état spécifique — lorsque isAnimated change, SwiftUI applique automatiquement la courbe. Pour l'animation spring, utilisez .spring(response: 0.4, dampingFraction: 0.6) au lieu de timingCurve.
L'équivalent ease-in-out sous Android via PathInterpolator. Les points de contrôle (0.42, 0.0, 0.58, 1.0) définissent la même courbe que sous iOS.
import android.animation.ObjectAnimator
import android.graphics.Path
import android.view.animation.PathInterpolator
import android.view.View
fun View.fadeInWithEase() {
val path = Path().apply {
cubicTo(0.42f, 0.0f, 0.58f, 1.0f, 1.0f, 1.0f)
}
val interpolator = PathInterpolator(path)
ObjectAnimator.ofFloat(this, "alpha", 0f, 1f).apply {
duration = 500
interpolator = interpolator
start()
}
}
PathInterpolator avec les points de contrôle (0.42, 0.0, 0.58, 1.0) est identique à CAMediaTimingFunction ease-in-out sous iOS. Pour les projets multiplateformes, utilisez les mêmes points de contrôle — cela garantit un comportement d'animation visuellement identique. Material Design recommande FastOutSlowInInterpolator (0.4, 0.0, 0.2, 1.0) comme alternative pour les interfaces spécifiques à Android.
Dans Compose, l'easing est défini via CubicBezierEasing avec les mêmes points de contrôle. Le résultat est une apparition fluide de l'élément avec transparence et décalage.
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
@Composable
fun EasingComposable() {
var visible by remember { mutableStateOf(false) }
val easing = CubicBezierEasing(0.42f, 0.0f, 0.58f, 1.0f)
val alpha by animateFloatAsState(
targetValue = if (visible) 1f else 0f,
animationSpec = tween(durationMillis = 500, easing = easing)
)
Column {
Button(onClick = { visible = !visible }) {
Text("Toggle")
}
Box(
modifier = Modifier
.size(100.dp)
.graphicsLayer(alpha = alpha)
.background(MaterialTheme.colorScheme.primary)
)
}
}
CubicBezierEasing dans Compose accepte les mêmes 4 paramètres de points de contrôle. animateFloatAsState interpole automatiquement la valeur alpha avec la courbe spécifiée. Pour comparaison : FastOutSlowInEasing dans Compose est équivalent à (0.4, 0.0, 0.2, 1.0) — la courbe standard de Material Design 3. Les courbes personnalisées via CubicBezierEasing offrent un contrôle total pour les animations de marque.
Foire aux questions
L'easing est un concept général de fonction de lissage utilisé sous iOS (CAMediaTimingFunction), sur le web (CSS ease-in-out) et en design. L'interpolator est l'implémentation de ce concept sous Android (TimeInterpolator). Essentiellement, c'est la même chose — une courbe mathématique de vitesse d'animation. La différence réside uniquement dans le nom et l'API de la plateforme. Dans les projets multiplateformes, le terme easing est utilisé comme universel.
iOS fournit CAMediaTimingFunction avec 5 fonctions prédéfinies : linear, easeIn, easeOut, easeInEaseOut et custom (Bézier cubique). Dans SwiftUI, Animation.easeIn, Animation.easeOut, Animation.easeInOut, Animation.spring et Animation.interpolatingSpring sont disponibles. Pour les courbes personnalisées, utilisez Animation.timingCurve(c0x, c0y, c1x, c1y, duration:).
Oui, si la même courbe Bézier cubique est définie. CAMediaTimingFunction sous iOS et PathInterpolator sous Android acceptent les points de contrôle Bézier cubique (x1, y1, x2, y2). Par exemple, la courbe ease-in-out (0.42, 0.0, 0.58, 1.0) donnera un comportement identique sur les deux plateformes. Material Design utilise cette approche pour les animations multiplateformes.
Utilisez les Directives de Mouvement de Material Design : pour l'apparition d'éléments — ease-out (départ rapide, fin fluide), pour la disparition — ease-in (fin rapide), pour le mouvement entre écrans — ease-in-out. Utilisez l'animation spring pour les éléments interactifs (boutons, cartes). Évitez le linéaire — il semble contre nature.
Les saccades (jank) se produisent lorsque l'animation n'atteint pas 60fps. Vérifiez : utilisez-vous CAMediaTimingFunction (pas une minuterie) sous iOS, PathInterpolator (pas Handler.post) sous Android. Des calculs lourds sur le thread principal, une hiérarchie de couches complexe ou un drawable volumineux peuvent entraîner des chutes d'images. Sous iOS, utilisez Core Animation avec shouldRasterize pour les couches complexes ; sous Android — l'accélération matérielle et le rendu GPU.
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