Pular para o conteúdo
charmotion

Como adicionar animação a um app Android (Compose)

Por equipe Charmotion · Atualizado em

Ilustração de capa do guia: Como adicionar animação a um app Android (Compose), com personagens de demonstração do Charmotion

Para adicionar animação a um app Android, use primeiro as APIs nativas do Compose: animate*AsState para valores únicos, AnimatedVisibility para mostrar e ocultar e rememberInfiniteTransition para loops. Adicione lottie-compose para loops vetoriais criados por designers, app.rive:rive-android para personagens interativos e o Media3 ExoPlayer para clipes de vídeo.

Principais pontos

  • A árvore de decisão do Google para o Compose associa cada caso a uma API, de animate*AsState a updateTransition.
  • O MotionLayout é para interfaces baseadas em Views; a documentação dele indica a animação do Compose para projetos novos.
  • O Google sugere um framework de terceiros, como o Lottie, para animações ilustradas complexas.
  • A API do Rive para Compose está em beta; o RiveAnimationView legado ainda oferece métodos simples de estado.
  • O Media3 tem um composable Player para vídeo em loop.

Já tem um personagem ou logo? Veja seu personagem em telas reais de app: telas de splash, onboarding, carregamento e estado vazio. É grátis, roda no navegador e não envia nada.

Qual API de animação do Compose serve para o seu caso?

A página "Choose an animation API" do Google funciona como uma árvore de decisão. Em resumo:

CasoAPI
Um valor com destino conhecido (tamanho, cor, alfa)animate*AsState, por exemplo animateFloatAsState
Mostrar ou ocultar conteúdoAnimatedVisibility
Trocar um composable por outroAnimatedContent ou Crossfade
Várias propriedades que mudam juntasupdateTransition
Um loop que roda sem pararrememberInfiniteTransition
Movimento controlado por gestosAnimatable
Microanimação simples de íconeAnimatedVectorDrawable
Arte ilustrada complexaUm framework de terceiros, como o Lottie

Acrescente uma linha que a tabela do Google não tem: um personagem que reage a eventos do app. Isso exige um runtime com estados, como o Rive, para que um único arquivo fique em idle, rode em loop durante o carregamento e reaja ao sucesso. Para um clipe fixo, use vídeo.

Se o seu app ainda usa Views, o MotionLayout continua disponível, mas a documentação dele agora começa com um aviso "Try the Compose way" que direciona para as APIs de animação do Compose.

Como funcionam o animate*AsState e o AnimatedVisibility?

Esses dois cobrem a maior parte do feedback de interface. O animateFloatAsState anima em direção ao destino que você passar, sempre que esse destino mudar:

@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")
        }
    }
}

O AnimatedVisibility faz o conteúdo aparecer e desaparecer com fade e expansão por padrão, e aceita transições personalizadas de entrada e saída.

Quando vários valores precisam se mover juntos, como o tamanho, a cor e a elevação de um card ao ser selecionado, use updateTransition. Ele vincula todas as animações filhas a um único estado, então elas começam no mesmo instante e ficam sincronizadas.

Para loops, o rememberInfiniteTransition roda até o composable sair da tela:

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))

Como animar mudanças de conteúdo e ícones no Compose?

Quando um elemento da interface substitui outro, como um contador, um rótulo de status ou um estado vazio que vira uma lista, use AnimatedContent. Ele anima a saída do conteúdo antigo e a entrada do novo sempre que o estado de destino muda:

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

O Crossfade é uma alternativa mais simples: ele apenas faz um fade entre dois layouts, o que funciona bem para alternar entre abas ou entre os estados de carregamento e carregado. Para mudanças de tamanho dentro de um único composable, adicione Modifier.animateContentSize() e o contêiner cresce ou encolhe suavemente quando o conteúdo muda. Em listas, o Modifier.animateItem() anima os itens quando eles são inseridos, removidos ou reordenados.

Para ícones, a árvore de decisão do Google recomenda o AnimatedVectorDrawable para microanimações SVG simples, como um ícone de play que vira pause ou um check que se desenha sozinho. Ele é um recurso vector drawable com animators anexados, então é distribuído como XML e não precisa de nenhuma biblioteca extra. Use-o para movimentos de ícone pequenos e com uma única função, e deixe o Lottie para ilustrações completas.

As transições de navegação ficam a cargo da biblioteca de navegação: os destinos composable() do Compose Navigation aceitam enterTransition e exitTransition, então as trocas de tela são animadas sem código personalizado.

Como adicionar uma animação Lottie a uma tela Compose?

Adicione o artefato Compose do projeto lottie-android da Airbnb, usando a versão indicada no README:

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

Coloque o .json exportado em res/raw/, carregue-o como uma composition e reproduza:

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

A composition carrega de forma assíncrona, então composition é null nos primeiros frames; o LottieAnimation simplesmente não desenha nada até ela ficar pronta. Se você precisa controlar a reprodução, por exemplo para tocar uma vez quando uma tarefa terminar, use animateLottieCompositionAsState e passe o progresso dele para o LottieAnimation.

O Lottie funciona bem para arte linear: um spinner, um check de sucesso, uma ilustração de onboarding. Se um ícone só precisa de um morph simples, a árvore do Google indica o AnimatedVectorDrawable, que não precisa de biblioteca extra. Se a arte precisa mudar de comportamento conforme o estado do app, considere o Rive.

Dez estados em loop de um personagem, cada um selecionado por um número de mood de 0 a 9
Os dez estados em loop em um arquivo Rive do Charmotion, selecionados com o input numérico mood.

Como adicionar uma animação Rive e alternar estados no Android?

Adicione app.rive:rive-android (11.9.2 na documentação atual do Rive) junto com androidx.startup e coloque o .riv em res/raw/. Chame Rive.init(context) uma vez na inicialização.

A API do Rive para Compose está marcada como beta e é onde o Rive concentra os novos desenvolvimentos. Ela precisa de um worker, carrega o arquivo de forma assíncrona e renderiza assim que o carregamento é concluído:

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

No Compose, arquivos com view models são controlados pela API de Data Binding do Rive; siga a seção de Compose da documentação do Rive para Android para ver as chamadas atuais.

A View API legada, construída em torno de RiveAnimationView, continua com suporte e tem um método para cada tipo de input. Ela funciona em layouts XML ou dentro do AndroidView do Compose:

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

Os nomes diferenciam maiúsculas de minúsculas. O guia de Rive no Android, Flutter e React Native traz a configuração completa.

Como reproduzir um vídeo em loop com Media3?

Para um clipe pré-renderizado, use o Media3 ExoPlayer. A documentação atual usa androidx.media3:media3-exoplayer:1.11.1, e o media3-ui-compose-material3 oferece um 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)
}

Definir volume como 0 deixa um loop decorativo em silêncio. Para clipes incluídos no app, use um MediaItem que aponte para o arquivo incluído em vez de uma URL remota, assim o clipe fica disponível offline e começa na hora.

Libere o player quando o composable sair da tela, senão ele continua decodificando em segundo plano. Vídeo é indicado para clipes com detalhes fotográficos ou 3D. Ele não reage a estados, então um personagem que precisa alternar entre idle e loading funciona melhor com Rive.

Como adicionar um mascote animado a um app Android?

Modele o mascote como uma função do estado da sua UI. A maioria dos apps já expõe um StateFlow a partir de um ViewModel; associe cada estado a um dos loops do mascote e dispare um trigger para eventos pontuais:

  • Tela em repouso: mood 0. Requisição em andamento: mood 5 (loading).
  • Requisição concluída: dispare success ou error e depois volte para idle.
  • Entrada de voz: um loop listening; resposta tocando: um loop talking.

Mantenha esse mapeamento em um só lugar. Assim as telas apenas emitem estados, e a state machine do arquivo cuida das transições, então uma reação de sucesso nunca é cortada de repente.

loading: mood 5, fica em loop enquanto o trabalho está em andamento.

O Charmotion gera esse tipo de arquivo a partir de um mascote ou logo enviado: um .riv com CharacterStateMachine (um input mood de 0 a 9, além de triggers como success, error, enter e exit) que funciona com as chamadas setNumberState e fireState acima, e um view model Character para Data Binding. Para planejar os estados, veja estados de animação de mascote.

Como manter as animações no Android fluidas e acessíveis?

Animações no Compose são leves quando alteram propriedades de desenho. Sempre que puder, anime alpha, escala e translação via graphicsLayer, e evite animar valores que obrigam um layout grande a ser medido de novo a cada frame.

  • Pare loops infinitos e libere os players quando a tela não estiver visível.
  • Use arquivos vetoriais pequenos para a arte da UI; deixe o vídeo para clipes que realmente precisam dele.
  • Teste em um aparelho de entrada, não só em um emulador num notebook rápido.

Respeite as configurações de movimento do usuário. No Android 9 e posteriores, é possível ativar Configurações > Acessibilidade > Remover animações, o que define a escala de duração do animador do sistema como zero. Verifique Settings.Global.ANIMATOR_DURATION_SCALE e, quando for 0, mostre um frame estático do mascote em vez de um loop. Mantenha o significado, como "carregando" ou "salvo", disponível em texto.

O guia de movimento reduzido explica quais loops a WCAG espera que o usuário consiga pausar ou evitar.

Adicione animação a um app Jetpack Compose

  1. Escolha a API. Use a árvore de decisão do Google: animate*AsState, AnimatedVisibility, updateTransition ou rememberInfiniteTransition.
  2. Adicione uma biblioteca para a arte. Adicione com.airbnb.android:lottie-compose para Lottie ou app.rive:rive-android com androidx.startup para Rive.
  3. Inclua o arquivo no app. Coloque os arquivos .json e .riv em res/raw/ com nomes em minúsculas e underscores.
  4. Renderize. Use LottieAnimation, o composable Rive() do Rive depois que rememberRiveFile for concluído com sucesso, ou o composable Player do Media3.
  5. Controle os estados. Associe o estado do ViewModel a números de mood e triggers, por exemplo setNumberState e fireState na view legada.
  6. Verifique as configurações de movimento. Mostre um frame estático quando a escala de duração do animador for 0.

Perguntas frequentes

O MotionLayout ainda é recomendado?

Só para telas baseadas em Views. O MotionLayout é construído sobre o ConstraintLayout e o sistema de Views, e a documentação dele agora começa com um aviso "Try the Compose way" que direciona projetos novos para as APIs de animação do Compose. Em um app Compose, use animate*AsState, AnimatedContent, updateTransition e APIs relacionadas.

AnimatedVectorDrawable ou Lottie?

Use AnimatedVectorDrawable para microanimações simples de ícones, como um ícone de play que se transforma em pause; não exige biblioteca extra. Use Lottie para animações ilustradas complexas feitas no After Effects. A árvore de decisão do Compose do Google traça a mesma linha entre ícones SVG simples e arte complexa.

Devo usar a API Compose do Rive ou o RiveAnimationView?

Use a API Compose em telas Compose novas, já que o Rive concentra o desenvolvimento nela, mas note que ela está marcada como beta. O RiveAnimationView continua com suporte e é a opção simples para layouts baseados em Views ou arquivos baseados em inputs controlados com setNumberState e fireState. O Compose pode hospedá-lo via AndroidView.

Como fazer uma animação no Compose rodar em loop para sempre?

Use rememberInfiniteTransition e anime um valor com infiniteRepeatable, por exemplo tween(800) com RepeatMode.Reverse para um efeito de pulsação. O loop roda enquanto o composable está na tela e para quando ele sai da composição. Para um arquivo Lottie, passe iterations = LottieConstants.IterateForever para LottieAnimation em vez de escrever seu próprio loop.

Respostas rápidas deste guia

Fontes

Guias relacionados