Compose dispose de nombreux mécanismes d'animation intégrés et il peut être difficile de savoir lequel choisir. Vous trouverez ci-dessous une liste de cas d'utilisation courants des animations. Pour en savoir plus sur l'ensemble des différentes options d'API disponibles, consultez la documentation complète sur les animations dans Compose.
Animer des propriétés composables courantes
Compose fournit des API pratiques qui vous permettent de résoudre de nombreux cas d'utilisation courants des animations. Cette section explique comment animer les propriétés courantes d'un composable.
Animation qui apparaît ou disparaît
Utilisez AnimatedVisibility
pour masquer ou afficher un composable. Les enfants dans AnimatedVisibility
peuvent utiliser Modifier.animateEnterExit()
pour leur propre transition d'entrée ou de sortie.
var visible by remember { mutableStateOf(true) } // Animated visibility will eventually remove the item from the composition once the animation has finished. AnimatedVisibility(visible) { // your composable here // ... }
Les paramètres d'entrée et de sortie de AnimatedVisibility
vous permettent de configurer le comportement d'un composable lorsqu'il apparaît et disparaît. Pour en savoir plus, consultez la documentation complète.
Une autre option pour animer la visibilité d'un composable consiste à animer la version alpha au fil du temps à l'aide de animateFloatAsState
:
var visible by remember { mutableStateOf(true) } val animatedAlpha by animateFloatAsState( targetValue = if (visible) 1.0f else 0f, label = "alpha" ) Box( modifier = Modifier .size(200.dp) .graphicsLayer { alpha = animatedAlpha } .clip(RoundedCornerShape(8.dp)) .background(colorGreen) .align(Alignment.TopCenter) ) { }
Cependant, la modification de la version alpha s'accompagne d'une mise en garde : le composable reste dans la composition et continue d'occuper l'espace dans lequel il est disposé. Cela peut amener les lecteurs d'écran et d'autres mécanismes d'accessibilité à prendre en compte l'élément à l'écran. En revanche, AnimatedVisibility
finit par supprimer l'élément de la composition.
Animer la couleur d'arrière-plan
val animatedColor by animateColorAsState( if (animateBackgroundColor) colorGreen else colorBlue, label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(animatedColor) } ) { // your composable here }
Cette option est plus performante que Modifier.background()
.
Modifier.background()
est acceptable pour un paramètre de couleur one-shot, mais lors de l'animation d'une couleur au fil du temps, cela peut entraîner plus de recompositions que nécessaire.
Pour animer indéfiniment la couleur d'arrière-plan, consultez la section Répéter une section d'animation.
Animer la taille d'un composable
Compose vous permet d'animer la taille des composables de différentes manières. Utilisez animateContentSize()
pour les animations entre les changements de taille des composables.
Par exemple, si votre zone de texte contient du texte pouvant passer d'une à plusieurs lignes, vous pouvez utiliser Modifier.animateContentSize()
pour effectuer une transition plus fluide:
var expanded by remember { mutableStateOf(false) } Box( modifier = Modifier .background(colorBlue) .animateContentSize() .height(if (expanded) 400.dp else 200.dp) .fillMaxWidth() .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { expanded = !expanded } ) { }
Vous pouvez également utiliser AnimatedContent
avec un SizeTransform
pour décrire comment les changements de taille doivent avoir lieu.
Animer la position du composable
Pour animer la position d'un composable, utilisez Modifier.offset{ }
en combinaison avec animateIntOffsetAsState()
.
var moved by remember { mutableStateOf(false) } val pxToMove = with(LocalDensity.current) { 100.dp.toPx().roundToInt() } val offset by animateIntOffsetAsState( targetValue = if (moved) { IntOffset(pxToMove, pxToMove) } else { IntOffset.Zero }, label = "offset" ) Box( modifier = Modifier .offset { offset } .background(colorBlue) .size(100.dp) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { moved = !moved } )
Si vous souhaitez vous assurer que les composables ne sont pas dessinés sur ou sous d'autres composables lors de l'animation de position ou de taille, utilisez Modifier.layout{ }
. Ce modificateur propage les changements de taille et de position au parent, ce qui affecte ensuite les autres enfants.
Par exemple, si vous déplacez un élément Box
dans un élément Column
et que les autres enfants doivent être déplacés en même temps que Box
, incluez les informations de décalage avec Modifier.layout{ }
comme suit:
var toggled by remember { mutableStateOf(false) } val interactionSource = remember { MutableInteractionSource() } Column( modifier = Modifier .padding(16.dp) .fillMaxSize() .clickable(indication = null, interactionSource = interactionSource) { toggled = !toggled } ) { val offsetTarget = if (toggled) { IntOffset(150, 150) } else { IntOffset.Zero } val offset = animateIntOffsetAsState( targetValue = offsetTarget, label = "offset" ) Box( modifier = Modifier .size(100.dp) .background(colorBlue) ) Box( modifier = Modifier .layout { measurable, constraints -> val offsetValue = if (isLookingAhead) offsetTarget else offset.value val placeable = measurable.measure(constraints) layout(placeable.width + offsetValue.x, placeable.height + offsetValue.y) { placeable.placeRelative(offsetValue) } } .size(100.dp) .background(colorGreen) ) Box( modifier = Modifier .size(100.dp) .background(colorBlue) ) }
Animer la marge intérieure d'un composable
Pour animer la marge intérieure d'un composable, utilisez animateDpAsState
en combinaison avec Modifier.padding()
:
var toggled by remember { mutableStateOf(false) } val animatedPadding by animateDpAsState( if (toggled) { 0.dp } else { 20.dp }, label = "padding" ) Box( modifier = Modifier .aspectRatio(1f) .fillMaxSize() .padding(animatedPadding) .background(Color(0xff53D9A1)) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { toggled = !toggled } )
Animer l'élévation d'un composable
Pour animer l'élévation d'un composable, utilisez animateDpAsState
en combinaison avec Modifier.graphicsLayer{ }
. Pour les changements d'altitude ponctuels, utilisez Modifier.shadow()
. Si vous animez l'ombre, l'option la plus performante est l'utilisation du modificateur Modifier.graphicsLayer{ }
.
val mutableInteractionSource = remember { MutableInteractionSource() } val pressed = mutableInteractionSource.collectIsPressedAsState() val elevation = animateDpAsState( targetValue = if (pressed.value) { 32.dp } else { 8.dp }, label = "elevation" ) Box( modifier = Modifier .size(100.dp) .align(Alignment.Center) .graphicsLayer { this.shadowElevation = elevation.value.toPx() } .clickable(interactionSource = mutableInteractionSource, indication = null) { } .background(colorGreen) ) { }
Vous pouvez également utiliser le composable Card
et définir la propriété d'élévation sur différentes valeurs par état.
Animer l'échelle, la traduction ou la rotation du texte
Lorsque vous animez l'échelle, la traduction ou la rotation d'un texte, définissez le paramètre textMotion
de TextStyle
sur TextMotion.Animated
. Cela garantit des transitions plus fluides entre les animations de texte. Utilisez Modifier.graphicsLayer{ }
pour traduire, faire pivoter ou mettre à l'échelle le texte.
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition") val scale by infiniteTransition.animateFloat( initialValue = 1f, targetValue = 8f, animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse), label = "scale" ) Box(modifier = Modifier.fillMaxSize()) { Text( text = "Hello", modifier = Modifier .graphicsLayer { scaleX = scale scaleY = scale transformOrigin = TransformOrigin.Center } .align(Alignment.Center), // Text composable does not take TextMotion as a parameter. // Provide it via style argument but make sure that we are copying from current theme style = LocalTextStyle.current.copy(textMotion = TextMotion.Animated) ) }
Animer la couleur du texte
Pour animer la couleur du texte, utilisez le lambda color
sur le composable BasicText
:
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition") val animatedColor by infiniteTransition.animateColor( initialValue = Color(0xFF60DDAD), targetValue = Color(0xFF4285F4), animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse), label = "color" ) BasicText( text = "Hello Compose", color = { animatedColor }, // ... )
Passer d'un type de contenu à un autre
Utilisez AnimatedContent
pour animer différents composables. Si vous souhaitez simplement un fondu standard entre les composables, utilisez Crossfade
.
var state by remember { mutableStateOf(UiState.Loading) } AnimatedContent( state, transitionSpec = { fadeIn( animationSpec = tween(3000) ) togetherWith fadeOut(animationSpec = tween(3000)) }, modifier = Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { state = when (state) { UiState.Loading -> UiState.Loaded UiState.Loaded -> UiState.Error UiState.Error -> UiState.Loading } }, label = "Animated Content" ) { targetState -> when (targetState) { UiState.Loading -> { LoadingScreen() } UiState.Loaded -> { LoadedScreen() } UiState.Error -> { ErrorScreen() } } }
AnimatedContent
peut être personnalisé pour afficher de nombreux types de transitions d'entrée et de sortie. Pour en savoir plus, consultez la documentation sur AnimatedContent
ou lisez cet article de blog sur
AnimatedContent
.
Lancer une animation pendant la navigation vers différentes destinations
Pour animer des transitions entre les composables lorsque vous utilisez l'artefact navigation-compose, spécifiez les éléments enterTransition
et exitTransition
sur un composable. Vous pouvez également définir l'animation par défaut à utiliser pour toutes les destinations du NavHost
de premier niveau :
val navController = rememberNavController() NavHost( navController = navController, startDestination = "landing", enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None } ) { composable("landing") { ScreenLanding( // ... ) } composable( "detail/{photoUrl}", arguments = listOf(navArgument("photoUrl") { type = NavType.StringType }), enterTransition = { fadeIn( animationSpec = tween( 300, easing = LinearEasing ) ) + slideIntoContainer( animationSpec = tween(300, easing = EaseIn), towards = AnimatedContentTransitionScope.SlideDirection.Start ) }, exitTransition = { fadeOut( animationSpec = tween( 300, easing = LinearEasing ) ) + slideOutOfContainer( animationSpec = tween(300, easing = EaseOut), towards = AnimatedContentTransitionScope.SlideDirection.End ) } ) { backStackEntry -> ScreenDetails( // ... ) } }
Il existe de nombreux types de transitions d'entrée et de sortie qui appliquent différents effets au contenu entrant et sortant. Pour en savoir plus, consultez la documentation.
Répéter une animation
Utilisez rememberInfiniteTransition
avec un animationSpec
infiniteRepeatable
pour répéter votre animation en continu. Modifiez RepeatModes
pour spécifier les allers-retours.
Utilisez finiteRepeatable
pour répéter un certain nombre de fois.
val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Green, targetValue = Color.Blue, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(color) } ) { // your composable here }
Démarrer une animation au lancement d'un composable
LaunchedEffect
s'exécute lorsqu'un composable entre dans la composition. Elle lance une animation au lancement d'un composable. Vous pouvez l'utiliser pour déclencher le changement d'état de l'animation. En utilisant Animatable
avec la méthode animateTo
pour démarrer l'animation au lancement:
val alphaAnimation = remember { Animatable(0f) } LaunchedEffect(Unit) { alphaAnimation.animateTo(1f) } Box( modifier = Modifier.graphicsLayer { alpha = alphaAnimation.value } )
Créer des animations séquentielles
Utilisez les API de coroutine Animatable
pour effectuer des animations séquentielles ou simultanées. Si vous appelez animateTo
sur Animatable
l'un après l'autre, chaque animation attend la fin des animations précédentes avant de continuer .
En effet, il s'agit d'une fonction de suspension.
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { alphaAnimation.animateTo(1f) yAnimation.animateTo(100f) yAnimation.animateTo(500f, animationSpec = tween(100)) }
Créer des animations simultanées
Utilisez les API de coroutine (Animatable#animateTo()
ou animate
) ou l'API Transition
pour réaliser des animations simultanées. Si vous utilisez plusieurs fonctions de lancement dans un contexte de coroutine, les animations sont lancées en même temps :
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { launch { alphaAnimation.animateTo(1f) } launch { yAnimation.animateTo(100f) } }
Vous pouvez utiliser l'API updateTransition
pour utiliser le même état afin de générer simultanément de nombreuses animations de propriétés différentes. L'exemple ci-dessous anime deux propriétés contrôlées par un changement d'état, rect
et borderWidth
:
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "transition") val rect by transition.animateRect(label = "rect") { state -> when (state) { BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = "borderWidth") { state -> when (state) { BoxState.Collapsed -> 1.dp BoxState.Expanded -> 0.dp } }
Optimiser les performances des animations
Les animations dans Compose peuvent entraîner des problèmes de performances. Cela est dû à la nature de l'animation: déplacement ou modification rapide des pixels à l'écran, image par image pour créer l'illusion de mouvement.
Examinez les différentes phases de Compose: la composition, la mise en page et le dessin. Si votre animation modifie la phase de mise en page, tous les composables concernés doivent être remis en page et redessinés. Si votre animation se produit lors de la phase de dessin, elle est par défaut plus performante que si vous l'exécutiez lors de la phase de mise en page, car elle nécessiterait moins de travail.
Pour que votre application en fasse le moins possible lors de l'animation, choisissez la version lambda d'un Modifier
si possible. Cette opération ignore la recomposition et exécute l'animation en dehors de la phase de composition. Sinon, utilisez Modifier.graphicsLayer{ }
, car ce modificateur s'exécute toujours lors de la phase de dessin. Pour en savoir plus à ce sujet, consultez la section Différer les lectures dans la documentation sur les performances.
Modifier la chronologie de l'animation
Par défaut, Compose utilise des animations de printemps pour la plupart des animations. Les ressorts, ou animations basées sur la physique, semblent plus naturels. Elles peuvent également être interrompues, car elles prennent en compte la vitesse actuelle de l'objet plutôt qu'une durée fixe.
Si vous souhaitez remplacer la valeur par défaut, toutes les API d'animation présentées ci-dessus peuvent définir un animationSpec
pour personnaliser l'exécution d'une animation, que vous souhaitiez qu'elle s'exécute pendant une certaine durée ou qu'elle soit plus dynamique.
Voici un récapitulatif des différentes options de animationSpec
:
spring
: animation basée sur la physique, définie par défaut pour toutes les animations. Vous pouvez modifier la raideur ou le taux d'amortissement pour modifier l'apparence de l'animation.tween
(abréviation de between): animation basée sur la durée, qui s'anime entre deux valeurs avec une fonctionEasing
.keyframes
: spécification permettant de spécifier des valeurs à certains points clés d'une animation.repeatable
: spécification basée sur la durée qui s'exécute un certain nombre de fois, spécifié parRepeatMode
.infiniteRepeatable
: spécification basée sur la durée qui s'exécute indéfiniment.snap
: se fixe instantanément à la valeur de fin, sans animation.
Lisez la documentation complète pour en savoir plus sur animationSpecs.
Ressources supplémentaires
Pour voir d'autres exemples d'animations amusantes dans Compose, consultez les ressources suivantes:
- 5 animations rapides dans Compose
- Faire avancer une méduse dans Compose
- Personnaliser
AnimatedContent
dans Compose - Présentation des fonctions de lissage de vitesse dans Compose