Lottie API Para Animações no Android
Blog /Android /Lottie API Para Animações no Android
(11124) (4)
Neste artigo vamos ao estudo completo da Lottie API, biblioteca criada pelo AirBnb com o objetivo de processar com maior eficiência e eficácia animações em sistemas mobile e Web. Mais precisamente, aqui estudaremos a Lottie API no Android.
Para melhor aproveitamento do estudo da API vamos, ao final da apresentação dela, a construção de um aplicativo de fuso horário mundial, app similar ao desenvolvido no conteúdo sobre a True Time API.
Neste novo projeto teremos trechos importantes com animações que aumentarão o "glamour" dele:
Animação no Android;
Lottie API:
Não desanime, temos a LottieFiles.com;
Animação local, folder /assets, com LottieAnimationView;
Animação local, folder /raw;
Animação remota, utilizando lottie_url;
Listener de atualização de animação;
Listener de início, de fim, de repetição e de cancelamento de animação;
Progresso de animação via propriedade progress;
Trabalhando a velocidade (duração) da animação;
Número e modelo de repetições;
Modificando a cor com lottie_colorFilter;
Animação com LottieDrawable;
KeyPath para a animação de partes separadas;
Mais opções de configuração;
Tamanho (altura x largura) da animação;
Tutorias para criação de animações no After Effects.
Classe de aplicação;
Broadcast de comunicação: Application para Activity;
AsyncTask ouvidor de novos horários;
ClockActivity, atividade principal.
Colocando animação no projeto:
Novo protótipo estático;
Baixando as animações que serão utilizadas;
Atualizando o layout XML de ClockActivity;
Classe container de animação Lottie;
Iniciando a LottieContainer;
Atualizando o background das opções de Spinner;
Há inúmeras APIs, nativas e não nativas, somente para animação no Android. A ObjectAnimator e a Facebook Keyframes provavelmente estão entre as mais populares, digo, populares depois da Lottie API.
Com exceção da Lottie, todas as outras APIs para animação no Android vêm carregadas de limitações:
Ou não é nada trivial a codificação para conseguir até mesmo simples animações sincronizadas;
Ou as funcionalidades da API não permitem que tenhamos em projeto nada melhor do que um GIF (ou PNG animado) com qualidade abaixo da esperada.
Então a Lottie API é perfeita, não tem limitações?
Sim, ela tem limitações. Mas o suporte é bem mais completo do que, por exemplo, o Android Vector Drawable (AVD), API nativa que não suporta o mesmo número de características do After Effects que a Lottie API suporta.
Lottie API
A biblioteca Lottie foi criada pelos engenheiros de desenvolvimento do AirBnb com o propósito de permitir animações mais completas em sistemas mobile e Web.
Aqui o termo "mais completas" é sinônimo de: maior suporte às animações criadas no Adobe After Effects, hoje um dos principais softwares no mercado para a criação de animações digitais.
O nome Lottie é uma homenagem a uma diretora alemã, Lotte Reiniger, que é a criadora da mais antiga filmagem de longa metragem ainda existente, o filme The Adventures of Prince Achmed de 1926.
As animações que rodarão junto aos códigos Lottie de seu projeto devem ser criadas no After Effects e então exportadas como arquivos JSON, exportação realizada junto ao uso do plugin open source Bodymovin.
Não desanime, temos a LottieFiles.com
Essa parte de: criar animação; e exportar da maneira correta. Isso não é nada trivial para nós desenvolvedores de software.
Eu particularmente não acredito ser um bom investimento em carreira focar também nos estudos de criação de animações, até porque não é algo que, para gerar qualidade, será aprendido da noite para o dia, terá uma curva de aprendizagem de médio longo prazo nessa jornada.
Mas não se preocupe caso você não tenha o conhecimento sobre o After Effects e nem mesmo um profissional de animação ao seu lado.
Nós programadores temos um site somente com animações open source: LottieFiles.com
As animações são todas preparadas para rodar na API Lottie, animações já exportadas em JSON com o uso do After Effects e Bodymovin.
É possível até mesmo editar as cores da animação escolhida depois de acionar "Customize with Bodymovin Editor":
Não deixe de acompanhar o projeto de exemplo deste artigo, pois nele utilizaremos duas animações diretamente da LottieFiles.
A API está no repositório jcenter(), logo, temos apenas que adicionar a referência a ela no Gradle App Level, ou build.gradle (Module: app):
implementation "com.airbnb.android:lottie:2.6.0-beta19"
Na época da construção deste artigo a versão mais atual e que rodava sem problemas as funcionalidades propostas pela API era a versão beta: 2.6.0-beta19.
Mas recomendo que somente utilize uma versão beta em seu projeto oficial, que irá a produção, se depois de uma bateria de testes for constatado que a API mesmo em versão beta roda sem problema algum.
Animação local, folder /assets, com LottieAnimationView
Agora a parte que mais empolga na API: os códigos simples para ter animações completas.
A LottieAnimationView é uma das classes presentes para executar animações, além de ser a mais recomendada na documentação oficial da Lottie API.
Antes de apresentar o código de exemplo vale informar que a LottieAnimationView herda de ImageView, ou seja, é possível utilizar todas as características de um ImageView utilizando somente a LottieAnimationView, mesmo para carregamento de imagens estáticas.
Assim um código XML de exemplo:
Animação local com arquivo JSON no folder /assets
android:id="@+id/lav_android_wave_json"
app:lottie_fileName="android_wave.json"
Como com qualquer outra View, podemos trabalhar toda a criação e configuração da LottieAnimationView também em código dinâmico, em programação. Mas eu fortemente recomendo que você deixe o que é estático em arquivo de conteúdo estático, como um file XML.
Os atributos lottie_ são muitos, vamos a explicação dos utilizados em código anterior:
lottie_autoPlay: inicia a animação dispensando a necessidade de invocação de playAnimation() em código;
lottie_fileName: permite que tanto animações .json quanto animações em .zip possam ser carregadas diretamente do folder /assets. Caso a animação android_wave.json estivesse, por exemplo, dentro do folder /assets/minhas_animacoes então a referência em lottie_fileName seria "minhas_animacoes/android_wave.json";
lottie_loop: indica se a animação vai continuar em repetição assim que finalizada.
Executando o código anterior, temos:
Confesso que fiquei confuso quando você informou sobre animações também em .zip. O que seria isso?
Um exemplo são as animações criadas com o uso de imagens, neste caso será exportado do After Effects, junto ao Bodymovin, um .zip contendo o arquivo JSON da animação vetorial e as imagens referenciadas neste arquivo JSON.
Animação local, folder /raw
Também é possível carregar animações locais diretamente do folder /drawable/raw, mas neste diretório somente animações .json é que serão carregadas. Veja o código de exemplo:
Animação local com arquivo JSON no folder /raw
android:id="@+id/lav_windmill"
app:lottie_rawRes="@raw/windmill" />
Dessa vez o atributo de referência a animação é o lottie_rawRes. Executando o código anterior, temos:
Animação remota, utilizando lottie_url
Para facilitar ainda mais a vida do desenvolvedor Android, temos a possibilidade de carregar animações diretamente da rede.
Toda a configuração de cache e requisição remota é abstraída do domínio do desenvolvedor, somente temos de colocar a url correta e ver a animação acontecer. Segue código de exemplo:
Animação JSON remota. Animações em .zip também podem ser
carregadas de origem remota.
android:id="@+id/lav_bullseye"
app:lottie_url="https://raw.githubusercontent.com/airbnb/lottie-android/master/LottieSample/src/main/res/raw/bullseye.json" />
O atributo lottie_url também aceita referencias a arquivos .zip. Executando o código anterior, temos:
Thiengo, então se eu quiser carregar alguma imagem remota utilizando o lottie_url conseguirei sem problemas?
Não. O atributo lottie_url é somente para animações After Effects em .json ou .zip. Imagens ainda têm de ser carregadas via android:src, digo, imagens locais. Para imagens remotas você pode utilizar o Universal Image Loader.
Lembrando que para carregamento remoto de animação é preciso ao menos a permissão de Internet no AndroidManifest.xml:
Importante: somente escolha por animações remotas se essa realmente for a melhor opção em seu domínio de problema, pois animações remotas dependem até mesmo da disponibilidade de banda do usuário.
Listener de atualização de animação
Para ouvir a cada nova atualização de frame na animação podemos implementar a Interface ValueAnimator.AnimatorUpdateListener como a seguir:
lav_time_zip.addAnimatorUpdateListener( this )
override fun onAnimationUpdate( animator: ValueAnimator? ) {
Assuma que aqui estamos utilizando o plugin kottlin-android-extensions e assim a propriedade lav_time_zip é equivalente ao ID de uma LottieAnimationView em tela.
Enquanto a animação estiver ocorrendo o método onAnimationUpdate() é invocado.
O parâmetro animator, do tipo ValueAnimator, nos permite acesso (e modificação) a algumas configurações de animação. Mas segundo meus testes, realizar atualizações em animação, quando necessário, ainda tem como melhor caminho o uso da instância de LottieAnimationView.
Listener de início, de fim, de repetição e de cancelamento de animação
Para ouvir os triggers de início, de fim, de repetição e de cancelamento de animação devemos implementar a Interface Animator.AnimatorListener. Veja o código a seguir:
Animator.AnimatorListener {
lav_time_zip.addAnimatorListener( this )
* Depois do primeiro loop completo, então inicia a
* repetição e ai sim o método abaixo é invocado para
* cada nova repetição.
override fun onAnimationRepeat( animator: Animator? ) {
* Para qualquer um dos métodos de Animator.AnimatorListener
* podemos aplicar o casting "animator as ValueAnimator"
* pois animator é do tipo ValueAnimator.
val valueAnimator = animator as ValueAnimator
* O método abaixo somente é invocado, ao final da animação, se
* a animação não estiver em loop.
override fun onAnimationEnd( animator: Animator? ) {
* Assim que a animação é bloqueada (e não finalizada
* normalmente), mesmo com a abertura de uma outra
* atividade, o método abaixo é invocado.
override fun onAnimationCancel( animator: Animator? ) {
* Método invocado assim que a animação inicia. Não funciona
* com animação que ocorre via propriedade progress em código
override fun onAnimationStart( animator: Animator? ) {
Como explicado em código acima, o parâmetro animator na verdade é também do tipo ValueAnimator como em onAnimationUpdate(), apresentado na seção anterior.
Importante: o conhecimento completo dos listerners Lottie é necessário, pois certamente você os utilizará para acionamento de funcionalidades de seu domínio de problema.
Progresso de animação via propriedade progress
É possível controlar o andamento da animação via código de programação, para isso temos de atualizar a propriedade progress como no código de exemplo a seguir.
Primeiro o trecho XML:
Animação controlada programaticamente pela propriedade
progress onde 0.0F é o valor mínimo e 1.0F é valor máximo.
android:id="@+id/lav_sun_in_a_cloud"
app:lottie_rawRes="@raw/sun_in_a_cloud" />
android:id="@+id/sb_animation"
Então o código Kotlin (poderia ser Java, com quase as mesmas configurações):
sb_animation.setOnSeekBarChangeListener(this)
* Listeners do SeekBar.
override fun onProgressChanged( seekBar: SeekBar?, progress: Int, fromUser: Boolean ) {
lav_sun_in_a_cloud.progress = progress / 100F
override fun onStartTrackingTouch( seekBar: SeekBar? ) {}
override fun onStopTrackingTouch( seekBar: SeekBar? ) {}
Como informado em código XML: independente da duração da animação, o valor que deve ser abordado em progress deve estar entre, incluindo eles, 0.0F e 1.0F.
O SeekBar foi utilizado para facilitar o exemplo, mas progress pode ser atualizado em qualquer contexto que esteja dentro da thread UI.
O trabalho com os frames da animação se faz necessário principalmente se você desenvolvedor Android for utilizar alguma animação baixada da Internet, ou seja, animação After Effects que não foi desenvolvida especificamente para o seu domínio de problema.
Com a configuração de frame é possível definir o ponto que a animação se inicia e o ponto que ela finaliza, mesmo se a configuração de loop estiver em true.
Antes de partirmos para o código de exemplo, veja quais são os métodos que permitem a configuração de frames:
setMinFrame( Int ): permiti definir em qual parte, frame, a animação se iniciará. O argumento é um Int entre 0 e o último frame existente na animação;
setMaxFrame( Int ): permiti definir em qual parte, frame, a animação finalizará. A configuração de argumento é como em setMinFrame();
setMinAndMaxFrame( Int, Int ): permiti a definição de frame mínimo e máximo em animação;
setMinProgress( Float ): permiti definir em qual ponto se iniciaria a animação. O argumento é do tipo Float e deve variar entre 0.0F e 1.0F;
setMaxProgress( Float ): permiti definir em qual ponto se finalizará a animação. A configuração de argumento é a mesma em setMinProgress();
setMinAndMaxProgress( Float, Float ): permiti, em uma única invocação, a definição de progresso mínimo e máximo;
frame: propriedade que permite definir, ou obter, o frame atual da animação em tela.
Caso você não tenha em mãos a informação de total de frames presente em uma animação, é possível utilizar a propriedade maxFrame para saber o total de frames nela:
val totalFrames = lav_animacao.maxFrame
Note que você somente obterá o valor real em maxFrame se a animação já estiver renderizada em tela, ou seja, pode ser que um código similar ao seguinte seja necessário:
SystemClock.sleep( 2000 ) /* Forçando um delay de 2 segundos. */
Log.i( "LOG", "Total frames: $totalFrames" )
Com isso podemos ir ao código de exemplo. Primeiro a parte XML, sem ainda a definição de min e max frame:
android:id="@+id/lav_video_cam"
app:lottie_rawRes="@raw/video_cam" />
Agora a parte de programação, já com a definição de min e max frame:
lav_video_cam.setMinFrame( 50 )
lav_video_cam.setMaxFrame( 100 )
A animação em teste tem um total de 180 frames, mas no código acima cortamos isso. Executando o algoritmo com o trecho anterior incluído, temos:
Animação de duração bem inferior, de 180 frames (aproximadamente 6 segundos) para 50 frames (aproximadamente 1.8 segundos).
Trabalhando a velocidade (duração) da animação
Com a propriedade speed é possível definir em qual velocidade a animação vai trabalhar. speed aceita um valor Float, onde 1.0F é o valor da velocidade padrão. Valores menores que 1.0F diminuem a velocidade da animação e valores maiores aumentam.
A seguir o código XML de exemplo de uma LottieAnimationView sem alteração em speed:
android:id="@+id/lav_isometric_gift_box"
app:lottie_rawRes="@raw/isometric_gift_box" />
Agora o código que acelera a animação:
lav_isometric_gift_box.speed = 3.5F
Uma animação de 4 segundos para agora aproximados 1.1 segundos.
Se um valor negativo for fornecido em speed, a animação trabalha de maneira invertida. Segue atualização do código anterior:
lav_isometric_gift_box.speed = -3.5F
Executando a última atualização, temos:
Número e modelo de repetições
É possível ser mais específico quanto a característica de loop e definir exatamente quantas vezes uma repetição deverá ocorrer. Também é possível definir o modo da repetição.
A seguir um LottieAnimationView de exemplo:
android:id="@+id/lav_grab"
app:lottie_rawRes="@raw/grab" />
Executando o trecho anterior, temos:
Agora definindo o repeatCount e o repeatMode:
lav_grab.repeatCount = 2
lav_grab.repeatMode = LottieDrawable.REVERSE
Executando novamente, agora com o trecho atualizado, temos:
A propriedade repeatCount define, ou retorna, quantas vezes a animação deve ser repetida. Se a contagem de repetições for 0, a animação nunca será repetida. Se a contagem de repetições for maior do que 0 ou então LottieDrawable.INFINITE, o modo de repetição será levado em consideração. O valor padrão é 0.
A propriedade repeatMode aceita os seguintes valores:
LottieDrawable.RESTART: quando a animação chega ao fim e repeatCount é LottieDrawable.INFINITE ou um valor positivo, a animação é reiniciada;
LottieDrawable.REVERSE: quando a animação chega ao final e repeatCount é LottieDrawable.INFINITE ou um valor positivo, a animação inverte a direção em cada nova iteração;
LottieDrawable.INFINITE (esta constante tem real efeito quando em repeatCount): valor utilizado com a propriedade repeatCount para repetir a animação indefinidamente.
Modificando a cor com lottie_colorFilter
Para modificar a cor total de uma animação é tão simples quanto ativar / desativar o loop dela. A seguir o LottieAnimationView de uma animação com cores originais.
android:id="@+id/lav_like"
app:lottie_rawRes="@raw/like" />
Agora o mesmo LottieAnimationView com o lottie_colorFilter configurado:
app:lottie_colorFilter="#3F51B5"
Executando o projeto com a nova configuração de animação, temos:
Note que o efeito positivo de lottie_colorFilter ocorre quando a animação tem transparência também na parte interna dela, caso contrário ficará tudo de uma única cor, ou seja, não haverá animação.
Animação com LottieDrawable
Além da API LottieAnimationView temos a API LottieDrawable que resumidamente nos permite: colocar drawables animados em componentes que aceitam arquivos drawables.
A seguir um ImageView de exemplo, View que receberá uma animação presente no folder /assets:
Agora o código dinâmico que ativa a animação no ImageView anterior:
val drawable = LottieDrawable()
LottieComposition.Factory.fromAssetFileName(
this, /* Contexto, atividade. */
"vigilance_camera.json" ) /* Path da animação no folder /assets. */
{ /* Lambda contendo um LottieComposition que permitirá a gerência via LottieDrawable. */
it -> drawable.composition = it
/* Iniciando a animação. */
drawable.playAnimation()
/* Colocando a animação, já ativada, no ImageView. */
iv_animation.setImageDrawable( drawable )
É importante ressaltar que o recomendado em documentação é o uso da API LottieAnimationView. São raros os casos onde a LottieDrawable poderá ser utilizada e a LottieAnimationView não.
Eu particularmente me interessei pela LottieDrawable para utiliza-la junto a alguma Spanned String, pois sei que é possível colocar drawables nesse tipo de String.
Porém em meus testes foi constatado que um drawable animado não é carregado em uma Spanned String. Se você teve resultados diferentes, não deixe de comentar aqui no artigo.
A LottieDrawable é pouco documentada e a maioria dos códigos utilizando ela, e disponíveis na comunidade Android, são códigos depreciados.
Quando realizei os testes a invocação LottieComposition.Factory.fromAssetFileName() era apresentada como depreciada, mas foi o único exemplo válido encontrado.
Logo, prefira o uso da LottieAnimationView, pois, em minha opinião, há fortes indícios de que a LottieDrawable não continuará como uma entidade pública.
Uma informação importante para aqueles que tentarão a LottieDrawable é que: ao final do processamento, quando a animação não é mais útil, o método recycleBitmaps() deve ser invocado, pode ser até mesmo no onDestroy() da atividade host da animação.
KeyPath para a animação de partes separadas
Um arquivo de animação gerado via After Effects junto ao Bodymovin, esse arquivo tem uma estrutura JSON hierárquica onde o todo se chama Composition e os objetos que integram o todo são denominados Layers.
Cada layer, camada, tem um nome e pode ser atualizado de maneira independente.
A seguir um LottieAnimationView que ainda não passou por nenhuma atualização independente de layer:
android:id="@+id/lav_postcard"
app:lottie_rawRes="@raw/postcard" />
Executando o projeto com o simples LottieAnimationView anterior, temos:
Agora o código onde modificaremos as cores das montanhas da animação de fotografia:
* Segundo alguns testes, as cores de itens individuais
* de uma animação somente são passíveis de serem atualizadas
* se utilizando uma instância de SimpleColorFilter ou
* de PorterDuffColorFilter.
val colorRed = SimpleColorFilter( Color.RED )
val colorBlue = PorterDuffColorFilter( Color.BLUE, PorterDuff.Mode.SRC_ATOP )
* A sintaxe correta para que a cor funcione, sintaxe de
* construtor: KeyPath( "key_name", "**" ). Segundo alguns
* testes, se não fizer assim não funciona, isso com a
* API versão 2.6.0-beta19.
lav_postcard.addValueCallback(
KeyPath( "Mountain front", "**" ),
LottieProperty.COLOR_FILTER,
LottieValueCallback<ColorFilter>( colorRed )
KeyPath( "Mountain back", "**" ),
LottieValueCallback<ColorFilter>( colorBlue )
Para aplicação de animação em layer, o método addValueCallback() deve ser utilizado, com os parâmetros:
KeyPath: que permite identificar, pelo nome, os layers que serão atualizados. A chave "**" indica a correspondência para zero ou mais layers. Para a definição de cor não foi possível colocar mais de um layer por método addValueCallback() e sempre, como último argumento em KeyPath, é necessária a definição de "**";
Property: que identifica a propriedade que será atualizada nos KeyPaths indicados em primeiro argumento. Aqui definimos que a propriedade de cor é que será atualizada, LottieProperty.COLOR_FILTER. Em Animatable Properties há todas as propriedades passíveis de atualização independente;
Callback: instância que contém a configuração de atualização da propriedade definida em segundo argumento. Tem que ser uma LottieValueCallback.
Os termos "Mountain front" e "Mountain back" foram encontrados no arquivo JSON de animação. Os nomes dos layers de sua animação estão sempre nas chaves nm.
Executando o projeto Android com o código anterior de atualização de layer, temos:
Agora a atualização de posicionamento da montanha de trás, a montanha azul:
val mountainBackPosition = LottieRelativePointValueCallback(
PointF( 100.9F, 81.99F )
KeyPath( "Mountain back" ),
LottieProperty.TRANSFORM_POSITION,
mountainBackPosition
Note que diferente da animação da propriedade de cor, aqui não precisamos da chave "**" para que a atualização ocorra.
Eu sei, isso é um pouco estranho, mas acredite, descobri isso na "martelada", pois a documentação sobre KeyPath na doc oficial do Lottie é bem simples, não diz muito em termos de código.
Executando o projeto com novo algoritmo, temos:
A seguir mais algumas opções de configuração que podem ser acessadas, algumas também podem ser atualizadas, além das já apresentadas até aqui.
Note que todas as configurações a seguir somente retornarão valores válidos se forem invocadas depois de a animação já estar renderizada em tela.
* Forçando um delay para que as solicitações somente ocorram
* depois da renderização de animação em tela.
SystemClock.sleep( 2000 )
val duration = lavAnim.duration
val repeatMode = lavAnim.repeatMode
val isAnimating = lavAnim.isAnimating
val hasMasks = lavAnim.hasMasks()
val hasMatte = lavAnim.hasMatte()
val imageAssetsFolder = lavAnim.imageAssetsFolder
val isMergePathsEnabledForKitKatAndAbove = lavAnim.isMergePathsEnabledForKitKatAndAbove
val scale = lavAnim.scale
val repeatCount = lavAnim.repeatCount
val useHardwareAcceleration = lavAnim.useHardwareAcceleration
val composition = lavAnim.composition
val performanceTracker = lavAnim.performanceTracker
Muitas das configurações apresentadas acima são bem especificas para aqueles profissionais que têm um conhecimento mais apurado sobre as necessidades das animações, por exemplo: a propriedade useHardwareAcceleration que permite um developer com expertise em animação tomar decisões de processamento com base no valor retornado desta entidade.
Tamanho (altura x largura) da animação
As animações criadas com o After Effects e exportadas com o plugin Bodymovin são animações vetoriais, ou seja, não há necessidade de criar animações com exacerbadas largura e altura.
A documentação oficial da Lottie API recomenda que as animações, quando o maior tamanho possível é necessário, sejam criadas nas dimensões 411px x 731px, pois a Lottie API transformará esses valores em DPs.
De qualquer forma, caso você tenha problemas com a cobertura da animação em tela, lembre que LottieAnimationView herda de ImageView e assim é possível utilizar com sucesso o atributo android:scaleType com os valore centerInside ou centerCrop.
A documentação não está atualizada ao menos em relação a versão mais atual da API na época da construção deste conteúdo, a versão 2.6.0-beta19;
Não há um mínimo exemplo funcional com a LottieDrawable. Se essa API não tem tanta importância assim ela poderia ser removida;
Os exemplos com KeyPath, animação independente de layer, também são escassos.
As animações podem ser invocadas com poucas linhas de código, exigindo um mínimo apenas em código XML, facilitando a construção do projeto;
Todo o trabalho de carregamento e gerência de animação em cache já é realizado pela própria API;
A herança do ImageView facilita em muito o enquadramento da animação em tela, quando necessário;
A possibilidade de carregamento de animação em servidor remoto é outro ponto de destaque. Como código extra somente temos de definir a permissão de Internet.
Animações, sem sombra de dúvidas, trarão maior profissionalismo ao seus projetos Android. A Lottie API permitirá que você consiga isso sem muito esforço.
Somente tome cuidado com a quantidade de animações carregadas em tela, pois em testes realizados foi constatado que quanto mais animação em tela, maior a necessidade de processamento e menos responsivo fica o aplicativo, afetando diretamente a experiência do usuário.
Lembre que caso você não tenha um artista de animações After Effects ao seu lado e também não tenha expertise em criação de animações com este software, você tem o site LottieFiles que certamente, junto a configurações LottieAnimationView, lhe permitirá obter o máximo de animação em seu domínio de problema.
Tutorias para criação de animações no After Effects
Caso você como desenvolvedor Android queira arriscar no aprendizado de animações no Adobe After Effects, a seguir deixo alguns tutoriais que poderão lhe ajudar com isso:
Tutorial BÁSICO de ANIMAÇÃO | After Effects;
COMO FAZER 5 ANIMAÇÕES INCRÍVEIS no AFTER EFFECTS com SHAPES!;
After Effects-Animação simples;
Tutorial Swift iOS: Exportando animações do After Effects com Bodymovin e Lottie;
Instalação e configuração do Lottie no After Effects.
Para projeto de exemplo vamos construir algo muito similar a ideia de aplicativo de fuso horário mundial, ideia desenvolvida no artigo sobre a True Time API.
Aqui teremos duas partes de desenvolvimento:
A primeira parte onde já teremos o aplicativo com a ClockImageView e a TrueTime API funcionando, mas sem animações Lottie;
A segunda parte onde iremos mudar o visual da aplicação colocando duas animações. Uma de "informativo de horário em uso" e outra para indicar que é de noite, ou de dia, no fuso horário atualmente escolhido.
Antes de prosseguir, saiba que os arquivos estáticos e todo o projeto finalizado está no GitHub dele em: https://github.com/viniciusthiengo/relgio-mundial-animado-lottie-kotlin-android.
De qualquer forma, não deixe de acompanhar o projeto de exemplo, pois nele discutiremos particularidades da Lottie API ainda não apresentadas até este ponto.
A seguir as imagens do protótipo estático desta primeira parte do aplicativo:
Tela com fuso horário definido
Nome da aplicação: Relógio Mundial Animado;
Ao final desta parte do app teremos a seguinte arquitetura de projeto:
Abaixo as configurações do Gradle Project Level, ou build.gradle (Project: RelgioMundialAnimado):
applicationId "thiengo.com.br.relgiomundialanimado"
Na segunda parte do projeto adicionaremos a referência a Lottie API. Por agora, se para as APIs acima tiver versões mais atuais, então escolha referenciar as versões mais novas.
No caso da True Time API certifique-se de que a versão mais atual não tem problemas de trava na UI.
package="thiengo.com.br.relgiomundialanimado">
<!-- Para que a True Time API funcione. -->
Para as configurações de tema e estilo, vamos iniciar com o arquivo de cores, /res/values/colors.xml:
<color name="colorPrimary">@color/colorSunSky</color>
<color name="colorPrimaryDark">@color/colorSunSky</color>
<color name="colorAccent">@color/colorSunSky</color>
<color name="colorInfo">@android:color/white</color>
<!-- Azul céu. -->
<color name="colorSunSky">#84D4F8</color>
<dimen name="paddingBottom">20dp</dimen>
<dimen name="paddingSide">20dp</dimen>
<dimen name="paddingTop">60dp</dimen>
Agora o arquivo de dimensões para aparelhos com o Android 19, KitKat, ou superior, o arquivo /res/values-v19/dimens.xml:
<dimen name="paddingBottom">60dp</dimen>
<dimen name="paddingSide">60dp</dimen>
<dimen name="paddingTop">100dp</dimen>
<string name="app_name">Relógio Mundial Animado</string>
Agora o arquivo que conterá os arrays estáticos do projeto. Segue /res/values/arrays.xml:
Por fim o arquivo de definição de tema de projeto, /res/values/styles.xml:
app de Relógio Mundial Animado.
Os dois últimos estilos definidos no XML anterior seguem uma convenção no Android para quando criamos estilos extras que estendem o estilo padrão, que em nosso caso é o estilo AppTheme. O prefixo dos estilos extras é o nome do tema base do app.
Aqui também estamos trabalhando com uma fonte personalizada, mais precisamente a fonte josefin_sans_regular.ttf, presente em /res/font.
Agora vamos ao código da TrueTimeApplication, classe responsável por conter o código de inicialização da True Time API:
* invocação a servidor NTP não remove da cache a TrueTime já
* TrueTime de servidor NTP, agora é preciso
Lembrando que a TrueTimeApplication já está sendo referenciada no AndroidManifest:
Broadcast de comunicação: Application para Activity
A seguir o código da classe BroadcastApplication que é responsável por permitir a comunicação da TrueTimeApplication para com a ClockActivity. Segue código:
Se quiser saber mais sobre LocalBroadcastManager, entre em: Como Utilizar o LocalBroadcastManager Para Comunicação no Android.
AsyncTask ouvidor de novos horários
Assim podemos ir a classe ouvidora de novos horários, mesmo aquele horário disponibilizado pelo aparelho. Abaixo o código de AsyncTrueTime:
date = TrueTimeRx.now() /* Horário servidor NTP. */
date = Date() /* Horário do aparelho. */
Com isso podemos prosseguir ao trecho final dessa primeira parte de projeto.
ClockActivity, atividade principal
Para a atividade principal vamos primeiro ao layout dela, /res/layout/activity_clock.xml:
android:id="@+id/rv_container"
android:paddingBottom="@dimen/paddingBottom"
android:paddingLeft="@dimen/paddingSide"
android:paddingRight="@dimen/paddingSide"
android:paddingTop="@dimen/paddingTop"
app:frameWidth="light"
app:pointerWidth="light"
Para o Spinner estamos utilizando um background personalizado. Em /res/drawable crie o arquivo spinner_border_and_background.xml como a seguir:
Com o XML anterior e o estilo para Spinners, este último definido no arquivo de tema do aplicativo, conseguiremos a seguinte formatação:
Abaixo o diagrama do layout activity_clock.xml:
Com isso podemos ir ao código Kotlin da ClockActivity:
* Colocando a barra de status (statusBar) e barra de
* navegação (bottomBar) em transparente.
if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ){
* a TrueTime API será solicitada sem que nós
AsyncTrueTime(this)
if(status) /* Origem: aparelho. */
else /* Origem: servidor NTP. */
* quanto o TextView de horário de acordo com o parâmetro
tv_clock.text = String.format( "%02d:%02d", hour, minute )
* será acionado assim que a API TrueTime tiver retorno de
Executando o projeto atual, temos:
Colocando animação no projeto
Apesar das animações já presentes, mais precisamente a animação da ClockImageView, é possível melhorar ainda mais a experiência do usuário.
A seguir os trechos do projeto que receberão animação:
O ícone estático de informe de "horário do aparelho em uso" dará lugar a um ícone animado;
Assim que houver a mudança de fuso horário, se for uma hora entre 6 da manhã e 17:59 da tarde, então uma animação de Sol deverá ocorrer. Caso contrário uma animação de Lua.
A seguir o fluxograma de acionamento da animação de "sol / lua" no app:
Novo protótipo estático
A seguir as imagens do novo protótipo estático do aplicativo:
Dia. Tela de relógio
Dia. Seletor de fuso horário aberto
Dia. Informe de horário de aparelho
Noite. Tela de relógio
Noite. Seletor de fuso horário aberto
Noite. Informe de horário de aparelho
Com isso podemos partir para a atualização do projeto.
O primeiro passo é adicionar a API Lottie no Gradle App Level, ou build.gradle (Module: app):
/* Biblioteca Lottie */
Baixando as animações que serão utilizadas
Aqui não precisaremos de expertise no After Effects, vamos baixar as duas animações necessárias diretamente do LottieFiles, são elas:
warning.json;
sun&moon.json.
Quando nas páginas das animações acima, clique no ícone de download para baixar os arquivos JSON.
Ao final, coloque:
O arquivo warning.json em /res/raw;
O arquivo sun_moon.json (renomeado) no folder /assets.
Atualizando o layout XML de ClockActivity
Agora a nova versão do layout activity_clock.xml, versão já contendo os dois LottieAnimationView:
View Lottie de animação, cobre toda a extensão da tela
disponível ao aplicativo. Devido a isso, e também para não
reconfigurarmos os paddings já definidos no RelativeLayout
do design, foi escolhido utilizar um FrameLayout como
android:id="@+id/lav_sun_moon"
android:contentDescription="@string/anim_sun_and_moon"
app:lottie_fileName="sun_moon.json"
app:lottie_loop="false" />
android:paddingTop="@dimen/paddingTop">
android:layout_above="@+id/ll_info_date"
app:lottie_colorFilter="@color/colorInfo"
app:lottie_rawRes="@raw/warning" />
Colocamos um novo layout container, FrameLayout, para não ser necessária toda a reconfiguração de padding já existente no RelativeLayout.
Colocamos também um novo LinearLayout para conter o ClockImageView e o TextView de rótulo de horário.
Note que no LottieAnimationView de ícone animado de informação estamos utilizando o atributo lottie_colorFilter para deixar a animação com a exata mesma cor do ícone estático anterior:
Para seguir as configurações de UI do novo protótipo estático, ainda é preciso atualizar as dimensões icon_width, icon_width, clock_width e clock_height no arquivo /res/values/dimens.xml:
<dimen name="clock_width">50dp</dimen>
<dimen name="clock_height">50dp</dimen>
<dimen name="icon_width">60dp</dimen>
<dimen name="icon_height">60dp</dimen>
Ainda temos uma atualização no arquivo de Strings. Em /res/values/strings.xml adicione o trecho em destaque:
<string name="anim_sun_and_moon">
Animação de Sol e Lua
A String anim_sun_and_moon está sendo referenciada como contentDescription do primeiro LottieAnimationView do layout.
A seguir o novo diagrama do layout activity_clock.xml:
Classe container de animação Lottie
Para o trabalho com gerência de animação, digo, gerência da animação de "Sol / Lua", vamos criar uma classe especifica para isso.
Assim não colocaremos mais uma responsabilidade na atividade principal e conseguiremos o mínimo possível de código limpo em projeto.
Vamos colocar essa nova classe em um novo pacote, logo, na raiz do projeto, crie o pacote /domain e em seguida, dentro do novo pacote, crie a classe LottieContainer como a seguir:
* Classe criada para ser responsável por manter toda a
* gerência da animação principal de ClockActivity.
class LottieContainer(
val animation: LottieAnimationView ) : Animator.AnimatorListener {
* Constantes que contém valores que possivelmente seriam
* alterados com frequência em projeto de produção. Essas
* constantes evitam que trabalhemos com valores mágicos,
* algo negativo na arquitetura limpa de projeto.
const val SPEED_SUN_TO_MOON = 1.5F
const val SPEED_MOON_TO_SUN = -1.5F
const val FRAME_FIRST = 46
const val FRAME_LAST = 82
* A importância do bloco de inicialização aqui é que a
* animação sempre estará com os dados iniciais corretos e
* componentes que têm mudança brusca de cor já estarão
* com as cores corretas.
animation.setMinAndMaxFrame( FRAME_FIRST, FRAME_LAST )
animation.addAnimatorListener( this )
startHourAndColor()
* Método que deve ser invocado no bloco inicial da classe
* (init{}) afim de obter a atual hora presente no device.
private fun startHourAndColor(){
hour = calendar.get( Calendar.HOUR_OF_DAY )
* Não há problemas em manter aqui os valores mágicos 6 e 18,
* pois essa é a definição de "dia (sol)" para o nosso domínio
* de problema.
private fun isMorning( hour: Int ): Boolean =
hour >= 6 && hour < 18
* Método responsável por verificar se a hora atualmente
* definida, de acordo com a seleção em Spinner, exige que a
* animação aconteça, ou seja, se for uma hora que indica "dia"
* e a animação atualmente esteja em "noite", então a animação
* de "noite" para "dia" deve ocorrer e vice-versa.
private fun canAnimate( hour: Int ): Boolean =
( isMorning( hour ) && animation.frame == FRAME_LAST )
|| ( !isMorning( hour ) && animation.frame == FRAME_FIRST )
* Método responsável pela verificação e então a ativação de
* animação, se necessária. Deve ser invocado sempre que uma
* nova hora for fornecida pelo sistema de horário construído
* para o aplicativo.
fun updateByHour( hour : Int ){
* Padrão Cláusula de Guarda para evitar o processamento
* do método caso a condição mínima não seja satisfeita.
* Mais sobre este padrão, no link a seguir:
* https://www.thiengo.com.br/padrao-de-projeto-clausula-de-guarda
if( !canAnimate( hour ) ){
* Quando a propriedade speed de LottieAnimationView tem
* um valor negativo a animação é realizada de maneira
* invertida. Ressaltando que speed é a velocidade da
* animação e não a definição da duração dela em
var speedValue = SPEED_SUN_TO_MOON /* Speed positivo. */
if( isMorning( hour ) ){
speedValue = SPEED_MOON_TO_SUN /* Speed negativo. */
animation.speed = speedValue
animation.playAnimation()
override fun onAnimationStart( animator: Animator? ) {}
* A cada final de animação o frame atual é atualizado.
* Caso essa lógica de negócio não fosse empregada, mesmo
* o modo visual da animação apontando para o primeiro
* frame a propriedade "frame", ao final da animação,
* passa a apontar sempre para o último frame da animação.
animation.frame =
if( isMorning( hour ) )
override fun onAnimationRepeat( animator: Animator? ) {}
override fun onAnimationCancel( animator: Animator? ) {}
Leia todos os comentários do código anterior, pois assim será fácil o entendimento dessa nova classe container.
Já lhe adianto que o entendimento completo do método startHourAndColor() virá em seções posteriores, logo, inicialmente, apenas assuma que a importância deste método é: inicializar a propriedade hour.
Vamos dar um destaque para o método onAnimationEnd():
* A cada final da animação o frame atual é atualizado.
Logo no bloco de inicialização da classe, init{}, nós definimos os frames inicial e final para a animação de "Sol / Lua". Isso utilizando o método setMinAndMaxFrame(). Essa configuração foi necessária, pois caso contrário a animação demoraria muito para uma simples mudança de status, como é na versão sem cortes de frames.
Quando o algoritmo estiver em teste em seu IDE, sem o uso do código em onAnimationEnd(), você notará que mesmo quando a animação inversa, speed com valor negativo, é invocada o valor de frame continua sendo o de "último frame de animação" (82), mesmo o visual da animação apontando para o minFrame (46) definido em setMinAndMaxFrame().
Isso provavelmente é um bug da API.
Resumo que temos com o bug encontrado: mesmo que você tenha APIs para quase todas as funcionalidades do aplicativo, em algum momento você terá de criar na unha a lógica de negócio que ajusta aquele 1% não encontrado em APIs e fóruns.
Iniciando a LottieContainer
Agora na ClockActivity coloque os códigos de inicialização e gerência da LottieContainer. Segue em destaque:
lateinit var lottieContainer: LottieContainer
* Iniciando o objeto container das configurações e métodos
* de ação sobre a LottieAnimationView principal de projeto.
lottieContainer = LottieContainer( this, lav_sun_moon )
fun updateClock( trueTime: Calendar ){
lottieContainer.updateByHour( hour )
Atualizando o background das opções de Spinner
A cor de background da lista de opções de fusos horários não está acompanhando a cor da tela:
No arquivo de cores do projeto, /res/values/colors.xml, adicione a seguinte nova cor:
<color name="colorMoonSky">#617F8C</color>
Na ClockActivity adicione o método setViewsColorByTime():
* Permite a mudança de cor dos componentes visuais da
* ClockActivity que precisam acompanhar o design de acordo
* com a animação em tela.
fun setViewsColorByTime( isMorning: Boolean ){
* Definição de cor de background da lista de opções do
* Spinner de GMTs.
sp_countries.setPopupBackgroundResource( R.color.colorSunSky )
sp_countries.setPopupBackgroundResource( R.color.colorMoonSky )
Agora na classe LottieContainer adicione os códigos em destaque:
updateActivityViewsColor()
private fun updateActivityViewsColor(){
(context as ClockActivity)
.setViewsColorByTime( isMorning( hour ) )
Com isso podemos partir para os testes e resultados.
Iniciando o app Android e realizando algumas mudanças em Spinner, temos:
Lembrando que a animação de informe de "horário de aparelho" somente fica em tela até o momento que nenhum data NTP é retornada.
Com isso terminamos o estudo da Lottie API. Essa é uma das APIs que eu lhe aconselho a já buscar algum lugar em seu aplicativo Android para se beneficiar dela.
Com isso, não deixe de se inscrever na 📩 lista de emails do Blog, logo acima ou ao lado, para receber em primeira mão os conteúdos exclusivos sobre o dev Android.
Abaixo os slides com a apresentação completa da Lottie API:
A seguir os vídeos com a construção passo a passo do algoritmo de animação do projeto de fuso horário mundial animado:
Para acessar o projeto de exemplo, entre no GitHub a seguir: https://github.com/viniciusthiengo/relgio-mundial-animado-lottie-kotlin-android.
Imagens são importantes para ajudar no conteúdo, mas animações conseguem ir além, ainda mais quando são interativas com as ações do usuário.
A Lottie API, mesmo com os problemas encontrados, é sem sombra de dúvidas a melhor opção para você aumentar o glamour de seu aplicativo Android colocando animações Adobe After Effects exportadas via Bodymovin plugin.
Não deixe de acompanhar a API, pois uma das promessas do time de engenheiros do AirBnb é o maior suporte as características do After Effects.
Assim finalizamos o artigo. Caso você tenha alguma dica ou dúvida sobre animações no Android, deixe logo abaixo nos comentários.
Documentação oficial Lottie
A nova era de animações mobile com Lottie!
android - objectanimator - json - lottie - animação - adobe - after effects - zip - bodymovin - airbnb
Excelente, como sempre sintetizando tudo. Thiengo, não estou conseguindo usar a Lottie beta e nem a 2.8.0 junto a api 26, meu target e compile são 26. Tem algo de errado ao usar essa API, você sabe, não achei nas minhas pequisas.
Estou sabendo por você sobre este problema. Quais as mensagens de erro que estão sendo apresentadas a ti?
De qualquer forma, a partir de Novembro todos os apps na Play Store deverão ter o target na API 28. Sendo assim você poderia já ir apontando para esta API, resolveria dois problemas.
como eu baixo seu video para estudar offline eu particularmente prefiro um video só do que quebrado em varios
Você pode utilizar os sites a seguir para baixar os vídeos e assisti-los offline:
Os vídeos curtos são uma recomendação do YouTube, pois assim a retenção (tempo assistindo) é bem maior (e está sendo).