Compose tiene muchos mecanismos de animación integrados, y puede ser abrumador saber cuál elegir. A continuación, se muestra una lista de casos de uso comunes de animaciones. Para obtener información más detallada sobre el conjunto completo de diferentes opciones de API disponibles, lee la documentación completa de Compose Animation.
Cómo animar propiedades comunes de componibilidad
Compose proporciona APIs convenientes que te permiten resolver muchos casos de uso comunes de animaciones. En esta sección, se muestra cómo animar propiedades comunes de un elemento componible.
Cómo animar la aparición y la desaparición
Usa AnimatedVisibility
para ocultar o mostrar un elemento componible. Los elementos secundarios dentro de AnimatedVisibility
pueden usar Modifier.animateEnterExit()
para su propia transición de entrada o salida.
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 // ... }
Los parámetros de entrada y salida de AnimatedVisibility
te permiten configurar el comportamiento de un elemento componible cuando aparece y desaparece. Lee la documentación completa para obtener más información.
Otra opción para animar la visibilidad de un elemento componible es animar la versión alfa a lo largo del tiempo usando 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) ) { }
Sin embargo, cambiar la versión alfa tiene la salvedad de que el elemento componible permanece en la composición y continúa ocupando el espacio en el que está diseñado. Esto podría provocar que los lectores de pantalla y otros mecanismos de accesibilidad sigan considerando el elemento en pantalla. Por otro lado, AnimatedVisibility
quita el elemento de la composición con el tiempo.
Cómo animar el color de fondo
val animatedColor by animateColorAsState( if (animateBackgroundColor) colorGreen else colorBlue, label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(animatedColor) } ) { // your composable here }
Esta opción tiene un mejor rendimiento que usar Modifier.background()
.
Modifier.background()
es aceptable para una configuración de color de una sola toma, pero, cuando se anima un color a lo largo del tiempo, esto podría causar más recomposiciones de las necesarias.
Para animar de forma infinita el color de fondo, consulta cómo repetir una sección de animación.
Cómo animar el tamaño de un elemento componible
Compose te permite animar el tamaño de los elementos componibles de varias maneras. Usa animateContentSize()
para las animaciones entre los cambios de tamaño de elementos componibles.
Por ejemplo, si tienes un cuadro con texto que puede expandirse de una a varias líneas, puedes usar Modifier.animateContentSize()
para lograr una transición más fluida:
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 } ) { }
También puedes usar AnimatedContent
, con un SizeTransform
para describir cómo deben realizarse los cambios de tamaño.
Cómo animar la posición de un elemento componible
Para animar la posición de un elemento componible, usa Modifier.offset{ }
combinado con 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 deseas asegurarte de que los elementos componibles no se dibujen sobre o debajo de otros elementos componibles al animar la posición o el tamaño, usa Modifier.layout{ }
. Este modificador propaga los cambios de tamaño y posición al elemento superior, que luego afecta a otros elementos secundarios.
Por ejemplo, si mueves un Box
dentro de un Column
y los otros elementos secundarios deben hacerlo cuando se mueve el Box
, incluye la información de desplazamiento con Modifier.layout{ }
de la siguiente manera:
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) ) }
Cómo animar el padding de un elemento componible
Para animar el padding de un elemento componible, usa animateDpAsState
combinado con 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 } )
Cómo animar la elevación de un elemento componible
Para animar la elevación de un elemento componible, usa animateDpAsState
combinado con Modifier.graphicsLayer{ }
. Para realizar cambios de elevación únicos, usa Modifier.shadow()
. Si estás animando la sombra, usar el modificador Modifier.graphicsLayer{ }
es la opción con mejor rendimiento.
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) ) { }
Como alternativa, usa el elemento componible Card
y establece la propiedad de elevación en valores diferentes por estado.
Cómo animar la escala, la traslación o la rotación de texto
Cuando animes la escala, la traslación o la rotación del texto, establece el parámetro textMotion
en TextStyle
en TextMotion.Animated
. Esto garantiza transiciones más fluidas entre las animaciones de texto. Usa Modifier.graphicsLayer{ }
para traducir, rotar o ajustar el tamaño del texto.
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) ) }
Cómo animar el color de texto
Para animar el color del texto, usa la lambda color
en el elemento BasicText
componible:
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 }, // ... )
Cómo alternar entre diferentes tipos de contenido
Usa AnimatedContent
para animar entre diferentes elementos componibles. Si solo quieres un atenuación estándar entre elementos componibles, usa 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
se puede personalizar para mostrar muchos tipos diferentes de transiciones de entrada y salida. Para obtener más información, lee la documentación sobre AnimatedContent
o consulta esta entrada de blog sobre
AnimatedContent
.
Realiza animaciones mientras navegas a diferentes destinos
Para animar transiciones entre elementos componibles cuando usas el artefacto navigation-compose, especifica enterTransition
y exitTransition
en un elemento componible. También puedes configurar la animación predeterminada que se usará para todos los destinos en el nivel superior 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( // ... ) } }
Hay muchos tipos diferentes de transiciones de entrada y salida que aplican diferentes efectos al contenido entrante y saliente. Consulta la documentación para obtener más información.
Cómo repetir una animación
Usa rememberInfiniteTransition
con un infiniteRepeatable
animationSpec
para repetir la animación de forma continua. Cambia RepeatModes
para especificar cómo debe ir y volver.
Usa finiteRepeatable
para repetir una cantidad determinada de veces.
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 }
Cómo iniciar una animación cuando se inicia un elemento componible
LaunchedEffect
se ejecuta cuando un elemento componible ingresa en la composición. Inicia una animación cuando se inicia un elemento componible. Puedes usarlo para impulsar el cambio de estado de la animación. Uso de Animatable
con el método animateTo
para iniciar la animación cuando se inicia:
val alphaAnimation = remember { Animatable(0f) } LaunchedEffect(Unit) { alphaAnimation.animateTo(1f) } Box( modifier = Modifier.graphicsLayer { alpha = alphaAnimation.value } )
Cómo crear animaciones secuenciales
Usa las APIs de corrutina de Animatable
para realizar animaciones secuenciales o simultáneas. Llamar a animateTo
en Animatable
una después de la otra hace que cada animación espere a que finalice las animaciones anteriores antes de continuar .
Esto se debe a que es una función de suspensión.
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { alphaAnimation.animateTo(1f) yAnimation.animateTo(100f) yAnimation.animateTo(500f, animationSpec = tween(100)) }
Cómo crear animaciones simultáneas
Usa las APIs de corrutinas (Animatable#animateTo()
o animate
), o la API de Transition
para lograr animaciones simultáneas. Si usas varias funciones de inicio en un contexto de corrutinas, se inician las animaciones al mismo tiempo:
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { launch { alphaAnimation.animateTo(1f) } launch { yAnimation.animateTo(100f) } }
Podrías usar la API de updateTransition
para usar el mismo estado y generar muchas animaciones de propiedades diferentes al mismo tiempo. En el siguiente ejemplo, se animan dos propiedades controladas por un cambio de estado, rect
y 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 } }
Cómo optimizar el rendimiento de las animaciones
Las animaciones en Compose pueden causar problemas de rendimiento. Esto se debe a la naturaleza de una animación: moverse o cambiar píxeles en la pantalla rápidamente, fotograma por fotograma para crear la ilusión de movimiento.
Considera las diferentes fases de Compose: composición, diseño y dibujo. Si la animación cambia la fase de diseño, deberás volver a diseñar y dibujar todos los elementos componibles afectados. Si tu animación ocurre en la fase de dibujo, tendrá, de forma predeterminada, más rendimiento que si se ejecutara la animación en la fase de diseño, ya que tendría menos trabajo que hacer en general.
Para asegurarte de que tu app haga lo menos posible durante la animación, elige la versión lambda de un objeto Modifier
siempre que sea posible. De esta manera, se omite la recomposición y se realiza la animación fuera de la fase de composición. De lo contrario, usa Modifier.graphicsLayer{ }
, ya que este modificador siempre se ejecuta en la fase de dibujo. Para obtener más información sobre esto, consulta la sección aplaza las lecturas en la documentación de rendimiento.
Cómo cambiar los tiempos de las animaciones
De forma predeterminada, Compose usa animaciones de resorte para la mayoría de las animaciones. Los resortes, o las animaciones basadas en la física, se sienten más naturales. También son interrumpibles, ya que tienen en cuenta la velocidad actual del objeto, en lugar de un tiempo fijo.
Si deseas anular el valor predeterminado, todas las APIs de Animation que se mostraron anteriormente tienen la capacidad de establecer un animationSpec
para personalizar el modo en que se ejecuta una animación, ya sea que desees que se ejecute durante un período determinado o que tenga más saltos.
A continuación, se muestra un resumen de las diferentes opciones de animationSpec
:
spring
: Animación basada en la física, el valor predeterminado para todas las animaciones. Puedes cambiar la rigidez o el dampingRatio para lograr un aspecto de animación diferente.tween
(abreviatura de between): Animación basada en la duración, que se anima entre dos valores con una funciónEasing
.keyframes
: Es la especificación para especificar valores en ciertos puntos clave de una animación.repeatable
: Especificación basada en la duración que se ejecuta una cierta cantidad de veces, especificada porRepeatMode
.infiniteRepeatable
: Especificación basada en la duración que se ejecuta de forma permanente.snap
: Se ajusta al valor final al instante sin ninguna animación.
Lee la documentación completa para obtener más información sobre animationSpecs.
Recursos adicionales
Para obtener más ejemplos de animaciones divertidas en Compose, consulta lo siguiente:
- 5 animaciones rápidas en Compose
- Cómo trasladar Jellyfish en Compose
- Cómo personalizar
AnimatedContent
en Compose - Aceleración de las funciones de aceleración en Compose