Guía rápida sobre las animaciones en Compose

Compose tiene muchos mecanismos de animación integrados y puede resultar 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 de animación comunes. En esta sección, se muestra cómo animar comunes propiedades de un elemento componible.

Cómo animar la aparición y la desaparición

Elemento componible verde que se muestra y se oculta
Figura 1: Anima la aparición y la desaparición de un elemento en una columna

Usa AnimatedVisibility para ocultar o mostrar un elemento componible. Niños en el interior AnimatedVisibility puede usar Modifier.animateEnterExit() para su propia entrada o salir de la transición.

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 cómo se comporta 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 transparencia con el 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 sigue siendo en la composición y sigue ocupando el espacio en el que está distribuida. Esto podría hacer 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 alfa de un elemento componible
Figura 2: Cómo animar el valor alfa de un elemento componible

Anima el color de fondo

Elemento componible con el color de fondo que cambia con el tiempo como una animación, en la que los colores se desvanecen entre sí.
Figura 3: Cómo animar el color de fondo de un elemento componible

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 un parámetro de configuración de color único, pero cuando se anima un color a lo largo del tiempo, esto podría causar más recomposiciones de las necesarias.

Para animar infinitamente el color de fondo, consulta cómo repetir una animación .

Anima el tamaño de un elemento componible

Elemento componible verde que anima su cambio de tamaño sin problemas.
Figura 4: Elemento componible que se anima de forma fluida entre un tamaño pequeño y uno más grande

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 los elementos componibles.

Por ejemplo, si tienes un cuadro con texto que se puede expandir de uno varias líneas, puedes usar Modifier.animateContentSize() para lograr una representación transición:

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 ocurrir los cambios de tamaño.

Cómo animar la posición de un elemento componible

Elemento componible verde que se anima de forma fluida hacia abajo y hacia la derecha
Figura 5: Elemento componible que se mueve por un desplazamiento

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 otros ni debajo de ellos cuando se anime la posición o el tamaño, usa Modifier.layout{ }. Esta el modificador propaga los cambios de tamaño y posición al elemento superior, que luego afecta con otros niños.

Por ejemplo, si mueves un Box dentro de un Column y el otro elemento secundario 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)
    )
}

2 cuadros con el segundo cuadro animando su posición X,Y, el tercer cuadro responde moviéndose también por la cantidad de Y.
Figura 6: Animación con Modifier.layout{ }

Anima el padding de un elemento componible

El elemento componible verde se vuelve más pequeño y grande cuando se hace clic, y se anima el padding.
Figura 7: Elemento componible con su padding animado

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

Figura 8: Animación de la elevación del elemento componible al hacer clic

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 deseas animar la sombra, usa 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 diferentes valores por estado.

Cómo animar la escala, la traslación o la rotación de texto

Texto componible que dice
Figura 9: Se reproduce el texto de forma fluida entre dos tamaños.

Cuando animes la escala, la traslación o la rotación del texto, establece el textMotion parámetro de TextStyle en TextMotion.Animated. Esto garantiza transiciones más fluidas entre las animaciones de texto. Usa Modifier.graphicsLayer{ } para traducir, rotar o ajustar el 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 del texto

Aparece la frase
Figura 10: Ejemplo de animación del color del 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 cambiar entre diferentes tipos de contenido

Texto en pantalla verde
Figura 11: Cómo usar AnimatedContent para animar cambios entre diferentes elementos componibles (en cámara lenta)

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, consulta la documentación sobre AnimatedContent o esta entrada de blog sobre AnimatedContent.

Cómo animar mientras se navega a diferentes destinos

Dos elementos componibles, uno verde que indica Landing y otro azul que dice Detalle. Para animarlos, desliza el elemento componible de detalles sobre el elemento componible de destino.
Figura 12: Cómo animar entre elementos componibles con navigation-compose

Para animar transiciones entre elementos componibles cuando usas el artefacto navigation-compose, especifica enterTransition y exitTransition en un elemento componible. También puedes establecer 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(
            // ...
        )
    }
}

Existen muchos tipos diferentes de transiciones de entrada y salida que se aplican diferentes efectos para el contenido entrante y saliente, consulta el documentación para obtener más información.

Cómo repetir una animación

Un fondo verde que se transforma en un fondo azul, infinitamente animando entre los dos colores.
Figura 13. Color de fondo animado entre dos valores, infinitamente

Usa rememberInfiniteTransition con un infiniteRepeatable. animationSpec para repetir la animación de forma continua. Cambiar RepeatModes a y 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 a la composición. Comienza una animación cuando se inicia un elemento componible, puedes usarla para impulsar la animación cambio de estado. Usa Animatable con el método animateTo para iniciar la animación al iniciar:

val alphaAnimation = remember {
    Animatable(0f)
}
LaunchedEffect(Unit) {
    alphaAnimation.animateTo(1f)
}
Box(
    modifier = Modifier.graphicsLayer {
        alpha = alphaAnimation.value
    }
)

Cómo crear animaciones secuenciales

Cuatro círculos con flechas verdes que se mueven entre cada uno y se animan una por una.
Figura 14: Diagrama que indica cómo avanza una animación secuencial, una por una.

Usa las APIs de corrutinas de Animatable para realizar animaciones secuenciales o simultáneas. Llamar a animateTo en Animatable uno después de las otras causas cada animación a esperar a que finalicen 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

Tres círculos con flechas verdes que se animan a cada uno y se animan todos al mismo tiempo.
Figura 15: Diagrama que indica cómo progresan las animaciones simultáneas, todo al mismo tiempo.

Usa las APIs de corrutinas (Animatable#animateTo() o animate) o la API de Transition para lograr animaciones simultáneas. Si usas varios inicia funciones en un contexto de corrutinas, lanza las animaciones al mismo tiempo hora:

val alphaAnimation = remember { Animatable(0f) }
val yAnimation = remember { Animatable(0f) }

LaunchedEffect("animationKey") {
    launch {
        alphaAnimation.animateTo(1f)
    }
    launch {
        yAnimation.animateTo(100f)
    }
}

Puedes 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
    }
}

Optimiza el rendimiento de la animación

Las animaciones en Compose pueden causar problemas de rendimiento. Esto se debe a la naturaleza de una animación: mover 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 tu animación cambia la fase de diseño, requiere que todos los elementos componibles afectados se vuelvan a diseñar y dibujar. Si la animación ocurre en la fase de dibujo, es por tendrá un mejor rendimiento de forma predeterminada que si ejecutaras la animación en el diseño ya que tendría menos trabajo para hacer en general.

Para garantizar que tu app haga lo menos posible durante la animación, elige la versión lambda de un Modifier siempre que sea posible. Esto omite la recomposición y 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 al respecto, consulta la sección aplaza las operaciones de lectura en la documentación de rendimiento.

Cómo cambiar el tiempo de animación

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 se pueden interrumpir, ya que tienen en cuenta la velocidad actual del objeto, en lugar de un tiempo fijo. Si quieres anular la configuración predeterminada, todas las APIs de animación 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 quieras que se ejecute durante una duración determinada o que sea más elástica.

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. Tú puede cambiar la rigidez o el valor de dampingRatio para lograr una animación diferente y la experiencia del usuario.
  • tween (abreviatura de between): Animación basada en la duración, animación. entre dos valores con una función Easing.
  • keyframes: 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, que especifica RepeatMode.
  • infiniteRepeatable: Especificación basada en la duración que se ejecuta para siempre.
  • snap: Se ajusta al valor final al instante sin ninguna animación.
Escribe tu texto alternativo aquí
Figura 16: Sin conjunto de especificaciones en comparación con un conjunto de especificaciones de Spring personalizado

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: