Ir al contenido
charmotion

Cómo agregar animación a una app de Android (Compose)

Por el equipo de Charmotion · Actualizado el

Ilustración de portada de la guía: Cómo agregar animación a una app de Android (Compose), con personajes de demostración de Charmotion

Para agregar animación a una app de Android, usa primero las APIs integradas de Compose: animate*AsState para valores individuales, AnimatedVisibility para mostrar y ocultar, y rememberInfiniteTransition para loops. Agrega lottie-compose para loops vectoriales creados por diseñadores, app.rive:rive-android para personajes interactivos y Media3 ExoPlayer para clips de video.

Puntos clave

  • El árbol de decisión de Compose de Google asigna cada caso a una API, desde animate*AsState hasta updateTransition.
  • MotionLayout es para interfaces basadas en Views; su documentación recomienda la animación de Compose para los proyectos nuevos.
  • Google sugiere un framework de terceros como Lottie para animaciones ilustradas complejas.
  • La API de Compose de Rive está en beta; el RiveAnimationView heredado todavía expone métodos de estado simples.
  • Media3 tiene un composable Player para video en loop.

¿Ya tienes un personaje o logo? Mira tu personaje en pantallas reales de una app: pantallas de splash, onboarding, carga y estado vacío. Es gratis, funciona en tu navegador y no sube nada.

¿Qué API de animación de Compose se ajusta a tu caso?

La página "Choose an animation API" de Google funciona como un árbol de decisión. En resumen:

CasoAPI
Un valor con un destino conocido (tamaño, color, alfa)animate*AsState, p. ej. animateFloatAsState
Mostrar u ocultar contenidoAnimatedVisibility
Reemplazar un composable por otroAnimatedContent o Crossfade
Varias propiedades que cambian juntasupdateTransition
Un loop que se ejecuta indefinidamenterememberInfiniteTransition
Movimiento controlado por gestosAnimatable
Microanimación simple de íconosAnimatedVectorDrawable
Arte ilustrado complejoUn framework de terceros como Lottie

Agrega una fila que la tabla de Google no tiene: un personaje que reacciona a los eventos de la app. Eso requiere un runtime con estados, como Rive, para que un solo archivo pueda estar en reposo, mantenerse en loop durante la carga y reaccionar a un éxito. Para un clip fijo, usa video.

Si tu app todavía usa Views, MotionLayout sigue disponible, pero su documentación ahora empieza con un aviso "Try the Compose way" que remite a las APIs de animación de Compose.

¿Cómo funcionan animate*AsState y AnimatedVisibility?

Estas dos cubren la mayor parte del feedback de la interfaz. animateFloatAsState anima hacia el destino que le pases, cada vez que ese destino cambia:

@Composable
fun PressableCard(pressed: Boolean, showBanner: Boolean) {
    val scale by animateFloatAsState(
        targetValue = if (pressed) 0.95f else 1f,
        label = "scale"
    )
    Column {
        Card(Modifier.graphicsLayer { scaleX = scale; scaleY = scale }) { /* ... */ }
        AnimatedVisibility(visible = showBanner) {
            Text("Saved")
        }
    }
}

AnimatedVisibility hace aparecer y desaparecer el contenido con un fundido y una expansión por defecto, y acepta transiciones de entrada y salida personalizadas.

Cuando varios valores deben moverse juntos, como el tamaño, el color y la elevación de una tarjeta al seleccionarla, usa updateTransition. Vincula todas las animaciones hijas a un mismo estado, para que empiecen en el mismo momento y se mantengan sincronizadas.

Para loops, rememberInfiniteTransition se ejecuta hasta que el composable sale de la pantalla:

val transition = rememberInfiniteTransition(label = "pulse")
val alpha by transition.animateFloat(
    initialValue = 0.4f,
    targetValue = 1f,
    animationSpec = infiniteRepeatable(tween(800), RepeatMode.Reverse),
    label = "alpha"
)
Box(Modifier.size(12.dp).alpha(alpha).background(Color.Gray, CircleShape))

¿Cómo se animan los cambios de contenido y los íconos en Compose?

Cuando un elemento de la interfaz reemplaza a otro, como un contador, una etiqueta de estado o un estado vacío que se convierte en una lista, usa AnimatedContent. Anima la salida del contenido anterior y la entrada del nuevo cada vez que cambia su estado de destino:

AnimatedContent(targetState = count, label = "count") { value ->
    Text("$value items")
}

Crossfade es una alternativa más simple: solo hace un fundido entre dos layouts, lo que sirve para cambiar entre pestañas o entre los estados de carga y cargado. Para cambios de tamaño dentro de un solo composable, agrega Modifier.animateContentSize() y el contenedor crecerá o se reducirá con suavidad cuando cambie su contenido. En listas, Modifier.animateItem() anima los elementos cuando se insertan, se eliminan o se reordenan.

Para íconos, el árbol de decisión de Google recomienda AnimatedVectorDrawable para microanimaciones SVG simples, como un ícono de reproducir que se convierte en pausa o una marca de verificación que se dibuja sola. Es un recurso vector drawable con animadores asociados, así que se distribuye como XML y no necesita ninguna biblioteca adicional. Úsalo para movimientos de íconos pequeños y de un solo propósito, y reserva Lottie para ilustraciones completas.

Las transiciones de navegación corresponden a la biblioteca de navegación: los destinos composable() de Compose Navigation aceptan enterTransition y exitTransition, así que los cambios de pantalla se animan sin código personalizado.

¿Cómo se agrega una animación de Lottie a una pantalla de Compose?

Agrega el artefacto de Compose del proyecto lottie-android de Airbnb, con la versión que indica su README:

implementation "com.airbnb.android:lottie-compose:$lottieVersion"

Coloca el .json exportado en res/raw/, cárgalo como composición y reprodúcelo:

@Composable
fun LoadingAnimation() {
    val composition by rememberLottieComposition(
        LottieCompositionSpec.RawRes(R.raw.loading)
    )
    LottieAnimation(
        composition,
        iterations = LottieConstants.IterateForever,
        modifier = Modifier.size(160.dp)
    )
}

La composición se carga de forma asíncrona, así que composition es null durante los primeros fotogramas; LottieAnimation simplemente no dibuja nada hasta que está lista. Si necesitas controlar la reproducción tú mismo, por ejemplo para reproducirla una vez cuando termina una tarea, usa animateLottieCompositionAsState y pasa su progreso a LottieAnimation.

Lottie sirve para arte lineal: un spinner, una marca de éxito, una ilustración de onboarding. Si un ícono solo necesita una transformación simple, el árbol de Google recomienda en su lugar AnimatedVectorDrawable, que no necesita ninguna biblioteca adicional. Si el arte debe cambiar de comportamiento según el estado de la app, considera Rive.

Diez estados en loop de un mismo personaje, cada uno seleccionado con un número de mood del 0 al 9
Los diez estados en loop de un archivo Rive de Charmotion, seleccionados con el input numérico mood.

¿Cómo se agrega una animación de Rive y se cambian sus estados en Android?

Agrega app.rive:rive-android (11.9.2 en la documentación actual de Rive) junto con androidx.startup, y coloca el .riv en res/raw/. Llama a Rive.init(context) una vez al iniciar la app.

La API de Compose de Rive está marcada como beta y es donde Rive concentra el desarrollo nuevo. Necesita un worker, carga el archivo de forma asíncrona y renderiza cuando la carga se completa correctamente:

setContent {
    val riveWorker = rememberRiveWorker()
    val riveFile = rememberRiveFile(
        RiveFileSource.RawRes.from(R.raw.character),
        riveWorker
    )
    when (riveFile) {
        is Result.Loading -> CircularProgressIndicator()
        is Result.Error -> Text("Animation unavailable")
        is Result.Success -> Rive(riveFile.value)
    }
}

En Compose, los archivos con view models se controlan mediante la API de Data Binding de Rive; sigue la sección de Compose de la documentación de Rive para Android para conocer las llamadas actuales.

La API de View heredada, construida en torno a RiveAnimationView, sigue siendo compatible y tiene un método por cada tipo de input. Funciona en layouts XML o dentro de AndroidView de Compose:

val mascot = findViewById<RiveAnimationView>(R.id.mascot)
mascot.setNumberState("CharacterStateMachine", "mood", 5f) // loading loop
mascot.fireState("CharacterStateMachine", "success")       // one-shot

Los nombres distinguen entre mayúsculas y minúsculas. La guía de Rive en Android, Flutter y React Native explica la configuración completa.

¿Cómo reproduces un video en loop con Media3?

Para un clip prerrenderizado, usa Media3 ExoPlayer. La documentación actual usa androidx.media3:media3-exoplayer:1.11.1, y media3-ui-compose-material3 ofrece un composable Player:

import androidx.media3.common.Player as Media3Player

@Composable
fun LoopingClip(uri: String) {
    val context = LocalContext.current
    val exo = remember {
        ExoPlayer.Builder(context).build().apply {
            setMediaItem(MediaItem.fromUri(uri))
            repeatMode = Media3Player.REPEAT_MODE_ONE
            volume = 0f
            prepare()
            play()
        }
    }
    DisposableEffect(Unit) { onDispose { exo.release() } }
    Player(player = exo)
}

Si pones volume en 0, un loop decorativo se mantiene en silencio. Para los clips que incluyes en la app, usa un MediaItem que apunte al archivo incluido en lugar de una URL remota, así el clip está disponible sin conexión y empieza al instante.

Libera el reproductor cuando el composable salga de pantalla; si no, seguirá decodificando en segundo plano. El video es ideal para clips con detalle fotográfico o 3D. No puede reaccionar al estado, así que para un personaje que tiene que cambiar entre reposo y carga conviene más Rive.

¿Cómo agregas una mascota de marca animada a una app de Android?

Modela la mascota como una función del estado de tu UI. La mayoría de las apps ya expone un StateFlow desde un ViewModel: asigna cada estado a uno de los loops de la mascota y dispara un trigger para los eventos puntuales:

  • Pantalla en reposo: mood 0. Solicitud en curso: mood 5 (loading).
  • Solicitud terminada: dispara success o error y luego vuelve al reposo.
  • Entrada de voz: un loop de escucha; respuesta en reproducción: un loop de habla.

Mantén esta asignación en un solo lugar. Así las pantallas solo emiten estados y la máquina de estados del archivo se encarga de las transiciones, por lo que una reacción de éxito nunca se corta de golpe.

loading: mood 5, se repite en loop mientras el trabajo está en curso.

Charmotion genera este tipo de archivo a partir de la mascota o el logo que subes: un .riv con CharacterStateMachine (un input mood de 0 a 9 más triggers como success, error, enter y exit) que funciona con las llamadas setNumberState y fireState de arriba, y un view model Character para Data Binding. Para planificar los estados, consulta estados de animación de una mascota.

¿Cómo mantienes las animaciones de Android fluidas y accesibles?

Las animaciones de Compose son ligeras cuando cambian propiedades de dibujo. Siempre que puedas, anima alpha, escala y traslación mediante graphicsLayer, y evita animar valores que obliguen a volver a medir un layout grande en cada fotograma.

  • Detén los loops infinitos y libera los reproductores cuando la pantalla no esté visible.
  • Usa archivos vectoriales ligeros para el arte de la UI; reserva el video para los clips que lo necesiten.
  • Prueba en un dispositivo de gama baja, no solo en un emulador en una laptop rápida.

Respeta la configuración de movimiento del usuario. En Android 9 y versiones posteriores, se puede activar Configuración > Accesibilidad > Quitar animaciones, que pone en cero la escala de duración de animaciones del sistema. Revisa Settings.Global.ANIMATOR_DURATION_SCALE y, cuando valga 0, muestra un fotograma fijo de tu mascota en lugar de un loop. Mantén el significado, como "cargando" o "guardado", disponible en texto.

La guía de movimiento reducido explica qué loops espera WCAG que los usuarios puedan pausar o evitar.

Agrega animación a una app de Jetpack Compose

  1. Elige la API. Usa el árbol de decisión de Google: animate*AsState, AnimatedVisibility, updateTransition o rememberInfiniteTransition.
  2. Agrega una biblioteca para el arte. Agrega com.airbnb.android:lottie-compose para Lottie o app.rive:rive-android más androidx.startup para Rive.
  3. Incluye el archivo en el bundle. Coloca los archivos .json y .riv en res/raw/ con nombres en minúsculas y guiones bajos.
  4. Renderízala. Usa LottieAnimation, el composable Rive() de Rive después de que rememberRiveFile se complete correctamente, o el composable Player de Media3.
  5. Controla los estados. Asigna el estado del ViewModel a números de mood y triggers, por ejemplo con setNumberState y fireState en la view heredada.
  6. Revisa la configuración de movimiento. Muestra un fotograma fijo cuando la escala de duración de animaciones sea 0.

Preguntas frecuentes

¿Se sigue recomendando MotionLayout?

Solo para pantallas basadas en Views. MotionLayout se basa en ConstraintLayout y en el sistema de Views, y su documentación ahora empieza con un aviso "Try the Compose way" que dirige los proyectos nuevos a las API de animación de Compose. En una app de Compose, usa animate*AsState, AnimatedContent, updateTransition y API relacionadas.

¿AnimatedVectorDrawable o Lottie?

Usa AnimatedVectorDrawable para microanimaciones simples de íconos, como un ícono de reproducir que se transforma en pausa; no necesita ninguna biblioteca adicional. Usa Lottie para animaciones ilustradas complejas hechas en After Effects. El árbol de decisión de Compose de Google traza la misma línea entre íconos SVG simples y arte complejo.

¿Debo usar la API de Compose de Rive o RiveAnimationView?

Usa la API de Compose para pantallas nuevas en Compose, ya que Rive concentra ahí su desarrollo, pero ten en cuenta que está marcada como beta. RiveAnimationView sigue siendo compatible y es la opción sencilla para layouts basados en Views o para archivos basados en inputs que se controlan con setNumberState y fireState. Compose puede alojarla mediante AndroidView.

¿Cómo hago que una animación de Compose se repita en loop infinito?

Usa rememberInfiniteTransition y anima un valor con infiniteRepeatable, por ejemplo tween(800) con RepeatMode.Reverse para un efecto de pulso. El loop se ejecuta mientras el composable está en pantalla y se detiene cuando sale de la composición. Para un archivo Lottie, pasa iterations = LottieConstants.IterateForever a LottieAnimation en lugar de programar tu propio loop.

Respuestas rápidas de esta guía

Fuentes

Guías relacionadas