
Olá Habr! No ano passado, Ivan Škorić, do PSPDFKit, falou no
MBLT DEV com um relatório sobre a criação de animações no Android baseado no Kotlin e na biblioteca RxJava.
Agora uso as técnicas do relatório para trabalhar no meu projeto, elas ajudam muito. Sob a cena, está a transcrição do relatório e do vídeo, agora você pode aproveitar esses truques.
Animação
No Android, existem 4 classes que se aplicam como se por padrão:
- ValueAnimator - Esta classe fornece um mecanismo de sincronização simples para executar animações que calculam valores animados e os definem para a Visualização.
- ObjectAnimator é uma subclasse de ValueAnimator que permite suportar animações para propriedades de objetos.
- AnimatorSet é usado para criar uma sequência de animação. Por exemplo, você tem uma sequência de animações:
- Veja as folhas à esquerda da tela.
- Depois de concluir a primeira animação, queremos executar uma animação de aparência para outra Visualização, etc.
- ViewPropertyAnimator - Inicia e otimiza automaticamente animações para a propriedade Exibir selecionada. Vamos usá-lo principalmente. Portanto, usaremos essa API e a colocaremos no RxJava como parte da programação reativa.
ValueAnimator
Vamos
analisar a estrutura
ValueAnimator . É usado para alterar o valor. Você especifica um intervalo de valores por meio de
ValueAnimator.ofFloat para o tipo de flutuador primitivo de 0 a 100. Especifique um valor de
Duração e inicie a animação.
Considere um exemplo:
val animator = ValueAnimator.ofFloat(0f, 100f) animator.duration = 1000 animator.start() animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener { override fun onAnimationUpdate(animation: ValueAnimator) { val animatedValue = animation.animatedValue as Float textView.translationX = animatedValue } })
Aqui, adicionamos o
UpdateListener e, a cada atualização, moveremos nossa Visualização horizontalmente e mudaremos sua posição de 0 para 100, embora essa não seja uma maneira muito boa de executar esta operação.
ObjectAnimator
Outro exemplo de implementação de animação é o ObjectAnimator:
val objectAnimator = ObjectAnimator.ofFloat(textView, "translationX", 100f) objectAnimator.duration = 1000 objectAnimator.start()
Damos a ele um comando para alterar o parâmetro View específico para o valor especificado para a View desejada e definir o tempo usando o método
setDuration . A conclusão é que sua classe deve ter o método
setTranslationX , o sistema encontrará esse método por meio de reflexão e, em seguida, a Visualização será animada. O problema é que a reflexão é usada aqui.
Animatorset
Agora considere a classe
AnimatorSet :
val bouncer = AnimatorSet() bouncer.play(bounceAnim).before(squashAnim1) bouncer.play(squashAnim1).before(squashAnim2) val fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f) fadeAnim.duration = 250 val animatorSet = AnimatorSet() animatorSet.play(bouncer).before(fadeAnim) animatorSet.start()
De fato, não é muito conveniente usar, especialmente para um grande número de objetos. Se você deseja criar animações mais complexas - por exemplo, defina um atraso entre a aparência das animações e quanto mais animações você desejar executar, mais difícil será controlá-las.
ViewPropertyAnimator
A classe final é
ViewPropertyAnimator . É uma das melhores classes para animar o View. Esta é uma ótima API para introduzir uma sequência de animações executadas:
ViewCompat.animate(textView) .translationX(50f) .translationY(100f) .setDuration(1000) .setInterpolator(AccelerateDecelerateInterpolator()) .setStartDelay(50) .setListener(object : Animator.AnimatorListener { override fun onAnimationRepeat(animation: Animator) {} override fun onAnimationEnd(animation: Animator) {} override fun onAnimationCancel(animation: Animator) {} override fun onAnimationStart(animation: Animator) {} })
Iniciamos o método
ViewCompat.animate , que retorna
ViewPropertyAnimator , e para a
tradução da animaçãoX
, definimos o valor como 50, para o parâmetro
translatonY como 100. Em seguida, especificamos a duração da animação, assim como o interpolador. O interpolador determina a sequência na qual as animações aparecem. Este exemplo usa um interpolador que acelera o início da animação e adiciona uma desaceleração no final. Também adicionamos um atraso para iniciar a animação. Além disso, temos um
AnimatorListener . Com ele, você pode se inscrever em determinados eventos que ocorrem durante a animação. Essa interface possui 4 métodos:
onAnimationStart ,
onAnimationCancel ,
onAnimationEnd ,
onAnimationRepeat .
Como regra, estamos interessados apenas em concluir a animação. No nível 16 da API
adicionado com EndAction:
.withEndAction({ //API 16+ //do something here where animation ends })
Nele, você pode definir a interface
Runnable e, após a conclusão de mostrar uma animação específica, uma ação é executada.
Agora, alguns comentários sobre o processo de criação de animações em geral:
- O método start () é opcional: assim que você chama o método animate () , uma sequência de animações é introduzida. Quando o ViewPropertyAnimator estiver configurado, o sistema iniciará a animação assim que estiver pronta para fazê-lo.
- Somente uma classe ViewPropertyAnimator pode animar apenas uma View específica. Portanto, se você deseja executar várias animações, por exemplo, deseja mover algo e, ao mesmo tempo, aumentar de tamanho, é necessário especificar isso em um animador.
Por que escolhemos o RxJava?
Vamos começar com um exemplo simples. Suponha que criamos um método fadeIn:
fun fadeIn(view: View, duration: Long): Completable { val animationSubject = CompletableSubject.create() return animationSubject.doOnSubscribe { ViewCompat.animate(view) .setDuration(duration) .alpha(1f) .withEndAction { animationSubject.onComplete() } } }
Essa é uma solução bastante primitiva e, para aplicá-la ao seu projeto, você precisará levar em consideração algumas nuances.
Vamos criar um
CompletableSubject , que usaremos para aguardar a conclusão das animações e, em seguida, usaremos o método
onComplete para enviar mensagens aos assinantes. Para executar animações sequencialmente, você precisa iniciar a animação não imediatamente, mas assim que alguém se inscrever. Dessa maneira, várias animações de estilo reativo podem ser executadas seqüencialmente.
Considere a animação em si. Nele, transferimos a Visualização, sobre a qual a animação será executada, e também indicamos a duração da animação. E como essa animação é aparência, devemos indicar a transparência 1.
Vamos tentar usar nosso método e criar uma animação simples. Suponha que tenhamos 4 botões na tela e queremos adicionar uma animação com a aparência de 1 segundo de duração:
val durationMs = 1000L button1.alpha = 0f button2.alpha = 0f button3.alpha = 0f button4.alpha = 0f fadeIn(button1, durationMs) .andThen(fadeIn(button2, durationMs)) .andThen(fadeIn(button3, durationMs)) .andThen(fadeIn(button4, durationMs)) .subscribe()
O resultado é um código conciso. Usando o operador
andThen ,
você pode executar animações sequencialmente. Quando subscrevemos, o evento
doOnSubscribe será enviado para
Completable , que é o primeiro da fila de execução. Após sua conclusão, ele assinará o segundo, terceiro e assim por diante. Portanto, se ocorrer um erro em algum estágio, a sequência inteira lançará um erro. Você também deve especificar alfa 0 antes do início da animação para que os botões fiquem invisíveis. E é assim que ficará:
Usando o
Kotlin , podemos usar as extensões:
fun View.fadeIn(duration: Long): Completable { val animationSubject = CompletableSubject.create() return animationSubject.doOnSubscribe { ViewCompat.animate(this) .setDuration(duration) .alpha(1f) .withEndAction { animationSubject.onComplete() } } }
Para a classe View, uma função de extensão foi adicionada. No futuro, não há necessidade de passar o argumento View para o método fadeIn. Agora você pode substituir todas as chamadas para View pela palavra-chave this dentro do método É disso que
Kotlin é capaz
.Vamos ver como a chamada para essa função em nossa cadeia de animação mudou:
button1.fadeIn(durationMs) .andThen(button2.fadeIn(durationMs)) .andThen(button3.fadeIn(durationMs)) .andThen(button4.fadeIn(durationMs)) .subscribe()
Agora o código parece mais compreensível. Ele afirma claramente que queremos aplicar animação com uma certa duração à exibição desejada. Usando o operador
andThen , criamos uma cadeia seqüencial de animações para o segundo, terceiro botão e assim por diante.
Sempre indicamos a duração das animações, esse valor é o mesmo para todas as telas - 1000 milissegundos.
Kotlin vem em socorro novamente. Podemos fazer um valor de tempo padrão.
fun View.fadeIn(duration: Long = 1000L):
Se você não especificar o parâmetro
duration , o tempo será automaticamente definido para 1 segundo. Mas se quisermos que o botão no número 2 aumente esse tempo para 2 segundos, basta especificar esse valor no método:
button1.fadeIn() .andThen(button2.fadeIn(duration = 2000L)) .andThen(button3.fadeIn()) .andThen(button4.fadeIn()) .subscribe()
Executando duas animações
Conseguimos executar uma sequência de animações usando o operador
andThen . E se eu precisar executar 2 animações ao mesmo tempo? Para fazer isso, existe um operador
mergeWith no
RxJava que permite combinar elementos
Completáveis de forma que eles sejam iniciados simultaneamente. Esta declaração inicia todos os elementos e termina após o último elemento ser exibido. Se
alterarmos e, em
seguida ,
mesclarmos , obteremos uma animação na qual todos os botões aparecerão ao mesmo tempo, mas o botão 2 aparecerá um pouco mais que os outros:
button1.fadeIn() .mergeWith(button2.fadeIn(2000)) .mergeWith(button3.fadeIn()) .mergeWith(button4.fadeIn()) .subscribe()
Agora podemos agrupar animações. Vamos tentar complicar a tarefa: por exemplo, queremos que os botões 1 e 2 apareçam ao mesmo tempo e, em seguida, os botões 3 e 4:
(button1.fadeIn().mergeWith(button2.fadeIn())) .andThen(button3.fadeIn().mergeWith(button4.fadeIn())) .subscribe()
Combinamos o primeiro e o segundo botões com o operador
mergeWith , repetimos a ação para o terceiro e o quarto e iniciamos esses grupos sequencialmente com o operador
andThen . Agora, melhoraremos o código adicionando o método
fadeInTogether :
fun fadeInTogether(first: View, second: View): Completable { return first.fadeIn() .mergeWith(second.fadeIn()) }
Isso permitirá que você execute a animação fadeIn para duas telas ao mesmo tempo. Como a cadeia de animação mudou:
fadeInTogether(button1, button2) .andThen(fadeInTogether(button3, button4)) .subscribe()
O resultado é a seguinte animação:
Considere um exemplo mais complexo. Suponha que precisamos mostrar uma animação com algum atraso. A declaração de
intervalo ajudará:
fun animate() { val timeObservable = Observable.interval(100, TimeUnit.MILLISECONDS) val btnObservable = Observable.just(button1, button2, button3, button4) }
Gerará valores a cada 100 milissegundos. Cada botão aparecerá após 100 milissegundos. A seguir, indicamos outro Observável que emitirá botões. Nesse caso, temos 4 botões. Usamos o operador
zip .

Diante de nós estão os fluxos de eventos:
Observable.zip(timeObservable, btnObservable, BiFunction<Long, View, Disposable> { _, button -> button.fadeIn().subscribe() })
O primeiro corresponde a
timeObservable . Este
Observável gera números em intervalos regulares. Suponha que serão 100 milissegundos.
O segundo
Observável gerará uma visualização. O operador
zip aguarda até o primeiro objeto aparecer no primeiro encadeamento e o conecta ao primeiro objeto a partir do segundo encadeamento. Apesar de todos esses 4 objetos no segundo segmento aparecerem imediatamente, ele aguardará até que os objetos comecem a aparecer no primeiro segmento. Assim, o primeiro objeto do primeiro fluxo se conectará ao primeiro objeto do segundo na forma de nossa exibição, e 100 milissegundos depois, quando um novo objeto aparecer, o operador o combinará com o segundo objeto. Portanto, a exibição aparecerá com um certo atraso.
Vamos
lidar com o
BiFinction no
RxJava . Esta função recebe dois objetos como entrada, realiza algumas operações neles e retorna um terceiro objeto. Queremos
dedicar tempo, visualizar objetos e ficar
descartáveis devido ao fato de chamarmos a animação
fadeIn e
assinarmos para
assinar . O valor do tempo não é importante para nós. Como resultado, obtemos esta animação:
Vanogogh
Vou falar sobre o
projeto que Ivan começou a desenvolver para o MBLT DEV 2017.
A biblioteca desenvolvida por Ivan apresenta várias conchas para animações. Já consideramos isso acima. Ele também contém animações prontas que você pode usar. Você obtém um conjunto generalizado de ferramentas para criar suas próprias animações. Esta biblioteca fornecerá componentes mais poderosos para programação reativa.
Considere a biblioteca usando um exemplo:
fun fadeIn(view:View) : AnimationCompletable { return AnimationBuilder.forView(view) .alpha(1f) .duration(2000L) .build().toCompletable() }
Suponha que você deseje criar uma animação que apareça, mas desta vez um
AnimationCompletable apareça em vez do objeto
Completable . Essa classe é herdada de
Completable , então mais funções aparecem agora. Uma característica importante do código anterior era que era impossível cancelar animações. Agora você pode criar um objeto
AnimationCompletable que faz com que a animação pare assim que cancelarmos a inscrição.
Crie uma animação emergente usando o
AnimationBuilder - uma das classes da biblioteca. Especifique a qual vista a animação será aplicada. Essencialmente, essa classe copia o comportamento do
ViewPropertyAnimator , mas com a diferença de que a saída é um fluxo.
Em seguida, defina alpha 1f e a duração é de 2 segundos. Depois, coletamos a animação. Assim que
invocamos a instrução
build , a animação aparece. Atribuímos às animações a propriedade de um objeto imutável, para que ele salve essas características para seu lançamento. Mas a animação em si não será iniciada.
Ligue
paraCompletable , que criará um
AnimationCompletable . Ele envolverá os parâmetros dessa animação em um tipo de shell para programação reativa e, assim que você se inscrever, iniciará a animação. Se você desativá-lo antes que o processo seja concluído, a animação será encerrada. Agora você também pode adicionar uma função de retorno de chamada. Você pode gravar os operadores
doOnAnimationReady ,
doOnAnimationStart ,
doOnAnimationEnd e similares:
fun fadeIn(view:View) : AnimationCompletable { return AnimationBuilder.forView(view) .alpha(1f) .duration(2000L) .buildCompletable() .doOnAnimationReady { view.alpha = 0f } }
Neste exemplo, mostramos como é conveniente usar o
AnimationBuilder e alteramos o estado de nossa visualização antes de iniciar a animação.
Denunciar vídeo
Vimos uma das opções para criar, compor e ajustar animações usando o Kotlin e o RxJava. Aqui está um link para um
projeto que descreve animações básicas e exemplos para eles, bem como os principais shells para trabalhar com animações.
Além da descriptografia, compartilho um vídeo do relatório:
Alto-falantes MBLT DEV 2018
Antes do
MBLT DEV 2018 ,
restam pouco mais de dois meses. Teremos as seguintes apresentações:
- Laura Morinigo, especialista em desenvolvedores do Google
- Kaushik Gopal, autor do Podcast Fragmentado
- Artyom Rudoi, Badoo
- Dina Sidorova, Google e outros .
Amanhã o preço do bilhete mudará.
Registre-se hoje.