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 des cas d'utilisation courants des animations. Pour en savoir plus sur l'ensemble des options d'API disponibles pour vous, consultez la documentation complète sur les animations dans Compose.
Animer les propriétés courantes des composables
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.
Animer l'apparition / la disparition
Utilisez AnimatedVisibility pour afficher ou masquer 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 l'
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) ) { }
Toutefois, la modification de l'alpha présente l'inconvénient que le composable reste dans la composition et continue d'occuper l'espace dans lequel il est mis en page. Cela peut amener les lecteurs d'écran et d'autres mécanismes d'accessibilité à continuer de considérer 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 l'utilisation de Modifier.background().
Modifier.background() est acceptable pour un paramètre de couleur unique, mais lorsque vous animez une couleur au fil du temps, cela peut entraîner plus de recompositions que nécessaire.
Pour animer la couleur d'arrière-plan à l'infini, consultez la section Répéter une animation section.
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 modifications de taille des composables.
Par exemple, si vous avez une zone contenant du texte qui peut passer d'une à plusieurs lignes, vous pouvez utiliser Modifier.animateContentSize() pour obtenir 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 modifications de taille doivent avoir lieu.
Animer la position du composable
Pour animer la position d'un composable, utilisez Modifier.offset{ } combiné à 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 au-dessus ou en dessous d'autres composables lors de l'animation de la position ou de la taille, utilisez Modifier.layout{ }. Ce modificateur propage les modifications de taille et de position au parent, ce qui affecte ensuite les autres enfants.
Par exemple, si vous déplacez une Box dans une Column et que les autres enfants doivent se déplacer lorsque la Box se déplace, 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) ) }
Modifier.layout{ }Animer la marge intérieure d'un composable
Pour animer la marge intérieure d'un composable, utilisez animateDpAsState combiné à 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 combiné à Modifier.graphicsLayer{ }. Pour les modifications d'élévation ponctuelles, utilisez Modifier.shadow(). Si vous animez l'ombre, l'utilisation du modificateur Modifier.graphicsLayer{ } est l'option la plus performante.
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 Card composable et définir la propriété d'élévation sur différentes valeurs par état.
Animer l'échelle, la translation ou la rotation du texte
Lorsque vous animez l'échelle, la translation ou la rotation du texte, définissez le textMotion
paramètre sur 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 }, // ... )
Basculer entre différents types de contenu
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 cet article de blog sur
AnimatedContent.
Animer lors de la navigation vers différentes destinations
Pour animer les transitions entre les composables lorsque vous utilisez l'artefact
navigation-compose, spécifiez les enterTransition et
exitTransition sur un composable. Vous pouvez également définir l'animation par défaut à utiliser pour toutes les destinations au niveau supérieur NavHost :
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 infiniteRepeatable
animationSpec pour répéter votre animation en continu. Modifiez RepeatModes pour spécifier comment elle doit aller et venir.
Utilisez repeatable pour répéter un nombre défini 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. Il démarre une animation au lancement d'un composable. Vous pouvez l'utiliser pour contrôler le changement d'état de l'animation. Utilisation de 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. L'appel de animateTo sur Animatable l'un après l'autre entraîne l'attente de chaque animation que les animations précédentes se terminent 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 obtenir 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 contrôler
de nombreuses animations de propriétés différentes en même temps. 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 d'une animation : déplacer ou modifier rapidement des pixels à l'écran, image par image, pour créer l'illusion du mouvement.
Tenez compte des différentes phases de Compose : composition, mise en page et dessin. Si votre animation modifie la phase de mise en page, elle nécessite que tous les composables concernés soient remis en page et redessinés. Si votre animation se produit dans la phase de dessin, elle est par défaut plus performante que si vous l'exécutiez dans la phase de mise en page, car elle aurait moins de travail à effectuer dans l'ensemble.
Pour vous assurer que votre application effectue le moins de tâches possible lors de l'animation, choisissez la version lambda d'un Modifier lorsque cela est possible. Cela ignore la recomposition et effectue
l'animation en dehors de la phase de composition. Sinon, utilisez
Modifier.graphicsLayer{ }, car ce modificateur s'exécute toujours dans la phase de dessin
. Pour en savoir plus, consultez la section Différer les lectures dans
la documentation sur les performances.
Modifier la chronologie de l'animation
Compose utilise par défaut des animations de rétroaction pour la plupart des animations. Les animations de rétroaction, ou basées sur la physique, sont plus naturelles. Elles peuvent également être interrompues, car elles tiennent compte de la vitesse actuelle de l'objet, au lieu d'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 sur une certaine durée ou qu'elle soit plus rebondissante.
Voici un résumé des différentes options animationSpec :
spring: animation basée sur la physique, valeur par défaut pour toutes les animations. Vous pouvez modifier la rigidité ou le rapport d'amortissement pour obtenir un aspect différent pour l'animation.tween(abréviation de between) : animation basée sur la durée, animation entre deux valeurs avec une fonctionEasing.keyframes: spécification pour définir 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: passe instantanément à la valeur finale sans aucune animation.
Pour en savoir plus sur les animationSpecs, consultez la documentation complète.
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
AnimatedContentdans Compose - Présentation des fonctions de lissage dans Compose