
Hola Habr! El año pasado, Ivan Škorić de PSPDFKit habló en
MBLT DEV con un informe sobre la creación de animaciones en Android basado en Kotlin y la biblioteca RxJava.
Ahora uso las técnicas del informe para trabajar en mi proyecto, me ayudan mucho. Debajo de la escena se encuentra la transcripción del informe y el video, ahora puede aprovechar estos trucos.
Animación
En Android, hay 4 clases que se aplican como si por defecto:
- ValueAnimator : esta clase proporciona un mecanismo de sincronización simple para ejecutar animaciones que calculan valores animados y los configuran para la Vista.
- ObjectAnimator es una subclase de ValueAnimator que le permite admitir animaciones para propiedades de objeto.
- AnimatorSet se usa para crear una secuencia de animación. Por ejemplo, tiene una secuencia de animaciones:
- Ver hojas a la izquierda de la pantalla.
- Después de completar la primera animación, queremos realizar una animación de apariencia para otra Vista, etc.
- ViewPropertyAnimator : inicia y optimiza automáticamente las animaciones para la propiedad de vista seleccionada. Lo usaremos principalmente. Por lo tanto, utilizaremos esta API y luego la pondremos en RxJava como parte de la programación reactiva.
ValueAnimator
Analicemos
el marco
ValueAnimator . Se usa para cambiar el valor. Puede especificar un rango de valores a través de
ValueAnimator.ofFloat para el tipo flotante primitivo de 0 a 100. Especifique un valor de
Duración e inicie la animación.
Considere un ejemplo:
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 } })
Aquí agregamos
UpdateListener y con cada actualización moveremos nuestra Vista horizontalmente y cambiaremos su posición de 0 a 100, aunque esta no es una muy buena manera de realizar esta operación.
ObjectAnimator
Otro ejemplo de implementación de animación es ObjectAnimator:
val objectAnimator = ObjectAnimator.ofFloat(textView, "translationX", 100f) objectAnimator.duration = 1000 objectAnimator.start()
Le damos un comando para cambiar el parámetro de Vista específico al valor especificado a la Vista deseada y establecer la hora usando el método
setDuration . La conclusión es que su clase debe tener el método
setTranslationX , luego el sistema encontrará este método a través de la reflexión, y luego la Vista se animará. El problema es que aquí se usa la reflexión.
Animatorset
Ahora considere la clase
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 hecho, no es muy conveniente de usar, especialmente para una gran cantidad de objetos. Si desea realizar animaciones más complejas, por ejemplo, establezca un retraso entre la aparición de las animaciones y cuantas más animaciones desee realizar, más difícil será controlarla.
ViewPropertyAnimator
La clase final es
ViewPropertyAnimator . Es una de las mejores clases para animar View. Esta es una gran API para presentar una secuencia de animaciones que ejecuta:
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 el método
ViewCompat.animate , que devuelve
ViewPropertyAnimator , y para la
traducción de animación
X establecemos el valor en 50, para el parámetro
translatonY en 100. Luego especificamos la duración de la animación, así como el interpolador. El interpolador determina la secuencia en la que aparecen las animaciones. Este ejemplo utiliza un interpolador que acelera el inicio de la animación y agrega una desaceleración al final. También agregamos un retraso para comenzar la animación. Además, tenemos un
AnimatorListener . Con él, puede suscribirse a ciertos eventos que ocurren durante la animación. Esta interfaz tiene 4 métodos:
onAnimationStart ,
onAnimationCancel ,
onAnimationEnd ,
onAnimationRepeat .
Como regla, solo estamos interesados en completar la animación. En API Nivel 16
agregado con EndAction:
.withEndAction({ //API 16+ //do something here where animation ends })
En él, puede definir la interfaz
Runnable , y después de completar la visualización de una animación específica, se realiza una acción.
Ahora algunos comentarios sobre el proceso de creación de animaciones en general:
- El método start () es opcional: tan pronto como llame al método animate () , se introduce una secuencia de animaciones. Cuando se configura ViewPropertyAnimator , el sistema comenzará la animación tan pronto como esté listo para hacerlo.
- Solo una clase ViewPropertyAnimator puede animar solo una vista específica. Por lo tanto, si desea realizar varias animaciones, por ejemplo, desea que algo se mueva y, al mismo tiempo, aumente de tamaño, debe especificar esto en un animador.
¿Por qué elegimos RxJava?
Comencemos con un ejemplo simple. Supongamos que creamos un 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() } } }
Esta es una solución bastante primitiva, y para aplicarla a su proyecto, deberá tener en cuenta algunos matices.
Vamos a crear un
CompletableSubject , que usaremos para esperar a que se completen las animaciones, y luego usaremos el método
onComplete para enviar mensajes a los suscriptores. Para ejecutar animaciones secuencialmente, debe iniciar la animación no inmediatamente, sino tan pronto como alguien se suscriba a ella. De esta forma, se pueden ejecutar varias animaciones de estilo reactivo de forma secuencial.
Considera la animación en sí. En él, transferimos la Vista, sobre la cual se realizará la animación, y también indicamos la duración de la animación. Y dado que esta animación es apariencia, debemos indicar la transparencia 1.
Tratemos de usar nuestro método y creemos una animación simple. Supongamos que tenemos 4 botones en la pantalla, y queremos agregarles una animación de la aparición de 1 segundo de duración:
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()
El resultado es un código tan conciso. Usando el operador
andThen , puede ejecutar animaciones secuencialmente. Cuando nos suscribamos, enviará el evento
doOnSubscribe a
Completable , que es el primero en la cola de ejecución. Después de su finalización, se suscribirá a la segunda, tercera y así sucesivamente en la cadena. Por lo tanto, si se produce un error en algún momento, la secuencia completa arroja un error. También debe especificar alfa 0 antes del inicio de la animación para que los botones sean invisibles. Y así es como se verá:
Usando
Kotlin , podemos usar las extensiones:
fun View.fadeIn(duration: Long): Completable { val animationSubject = CompletableSubject.create() return animationSubject.doOnSubscribe { ViewCompat.animate(this) .setDuration(duration) .alpha(1f) .withEndAction { animationSubject.onComplete() } } }
Para la clase View, se agregó una función de extensión. En el futuro, no es necesario pasar el argumento View al método fadeIn. Ahora puede reemplazar todas las llamadas a Ver con esta palabra clave dentro del método. Esto es lo que
Kotlin es capaz de hacer
.Veamos cómo ha cambiado la llamada a esta función en nuestra cadena de animación:
button1.fadeIn(durationMs) .andThen(button2.fadeIn(durationMs)) .andThen(button3.fadeIn(durationMs)) .andThen(button4.fadeIn(durationMs)) .subscribe()
Ahora el código se ve más comprensible. Establece claramente que queremos aplicar animación con una cierta duración a la visualización deseada. Usando el operador
andThen , creamos una cadena secuencial de animaciones para el segundo, tercer botón, y así sucesivamente.
Siempre indicamos la duración de las animaciones, este valor es el mismo para todas las pantallas: 1000 milisegundos.
Kotlin viene al rescate nuevamente. Podemos hacer un valor de tiempo predeterminado.
fun View.fadeIn(duration: Long = 1000L):
Si no especifica el parámetro de
duración , el tiempo se establecerá automáticamente en 1 segundo. Pero si queremos que el botón en el número 2 aumente este tiempo a 2 segundos, simplemente especificamos este valor en el método:
button1.fadeIn() .andThen(button2.fadeIn(duration = 2000L)) .andThen(button3.fadeIn()) .andThen(button4.fadeIn()) .subscribe()
Ejecutando dos animaciones
Pudimos ejecutar una secuencia de animaciones usando el operador
andThen . ¿Qué sucede si necesito ejecutar 2 animaciones al mismo tiempo? Para hacer esto, hay un operador
mergeWith en
RxJava que le permite combinar elementos
Completables de tal manera que se
inicien simultáneamente. Esta declaración inicia todos los elementos y termina después de que se muestra el último elemento. Si cambiamos y
luego fusionamos con , obtenemos una animación en la que todos los botones aparecen al mismo tiempo, pero el botón 2 aparecerá un poco más que los demás:
button1.fadeIn() .mergeWith(button2.fadeIn(2000)) .mergeWith(button3.fadeIn()) .mergeWith(button4.fadeIn()) .subscribe()
Ahora podemos agrupar animaciones. Intentemos complicar la tarea: por ejemplo, queremos que el botón 1 y el botón 2 aparezcan al mismo tiempo, y luego el botón 3 y el botón 4:
(button1.fadeIn().mergeWith(button2.fadeIn())) .andThen(button3.fadeIn().mergeWith(button4.fadeIn())) .subscribe()
Combinamos los botones primero y segundo con el operador
mergeWith , repetimos la acción para el tercero y cuarto, y comenzamos estos grupos secuencialmente con el operador
andThen . Ahora mejoraremos el código agregando el método
fadeInTogether :
fun fadeInTogether(first: View, second: View): Completable { return first.fadeIn() .mergeWith(second.fadeIn()) }
Le permitirá ejecutar la animación de fadeIn para dos vistas al mismo tiempo. Cómo ha cambiado la cadena de animación:
fadeInTogether(button1, button2) .andThen(fadeInTogether(button3, button4)) .subscribe()
El resultado es la siguiente animación:
Considere un ejemplo más complejo. Supongamos que necesitamos mostrar una animación con cierto retraso. La declaración de
intervalo ayudará:
fun animate() { val timeObservable = Observable.interval(100, TimeUnit.MILLISECONDS) val btnObservable = Observable.just(button1, button2, button3, button4) }
Generará valores cada 100 milisegundos. Cada botón aparecerá después de 100 milisegundos. A continuación, indicamos otro Observable que emitirá botones. En este caso, tenemos 4 botones. Usamos el operador
zip .

Ante nosotros están las corrientes de eventos:
Observable.zip(timeObservable, btnObservable, BiFunction<Long, View, Disposable> { _, button -> button.fadeIn().subscribe() })
El primero corresponde a
timeObservable . Este
observable generará números a intervalos regulares. Supongamos que serán 100 milisegundos.
El segundo
observable generará una vista. El operador
zip espera hasta que el primer objeto aparezca en el primer hilo y lo conecta con el primer objeto del segundo hilo. A pesar de que todos estos 4 objetos en el segundo hilo aparecerán inmediatamente, él esperará hasta que los objetos comiencen a aparecer en el primer hilo. Por lo tanto, el primer objeto de la primera secuencia se conectará al primer objeto del segundo en la forma de nuestra vista, y 100 milisegundos después, cuando aparezca un nuevo objeto, el operador lo combinará con el segundo objeto. Por lo tanto, la vista aparecerá con cierto retraso.
Tratemos con
BiFinction en
RxJava . Esta función recibe dos objetos como entrada, realiza algunas operaciones en ellos y devuelve un tercer objeto. Queremos tomar tiempo y ver objetos y obtener
Desechables debido al hecho de que llamamos a la animación
fadeIn y nos suscribimos para
suscribirnos . El valor del tiempo no es importante para nosotros. Como resultado, obtenemos esta animación:
Vanogogh
Te contaré sobre el
proyecto que Ivan comenzó a desarrollar para MBLT DEV 2017.
La biblioteca desarrollada por Ivan presenta varios proyectiles para animaciones. Ya hemos considerado esto arriba. También contiene animaciones listas para usar que puede usar. Obtiene un conjunto generalizado de herramientas para crear sus propias animaciones. Esta biblioteca le proporcionará componentes más potentes para la programación reactiva.
Considere la biblioteca usando un ejemplo:
fun fadeIn(view:View) : AnimationCompletable { return AnimationBuilder.forView(view) .alpha(1f) .duration(2000L) .build().toCompletable() }
Suponga que desea crear una animación que aparece, pero esta vez aparece un
AnimationCompletable en lugar del objeto
Completable . Esta clase hereda de
Completable , por lo que ahora aparecen más funciones. Una característica importante del código anterior era que era imposible cancelar animaciones. Ahora puede crear un objeto
AnimationCompletable que haga que la animación se detenga tan pronto como nos demos de baja.
Cree una animación emergente utilizando
AnimationBuilder , una de las clases de la biblioteca. Especifique a qué Vista se aplicará la animación. En esencia, esta clase copia el comportamiento de
ViewPropertyAnimator , pero con la diferencia de que la salida es una secuencia.
A continuación, configure alfa 1f y la duración es de 2 segundos. Luego recogemos la animación. Tan pronto como
invoquemos la declaración de
compilación , aparece la animación. Asignamos animaciones a la propiedad de un objeto inmutable, por lo que guardará estas características para su lanzamiento. Pero la animación en sí no comenzará.
Llama a
toCompletable , que creará un
AnimationCompletable . Envolverá los parámetros de esta animación en una especie de shell para la programación reactiva, y tan pronto como se suscriba, comenzará la animación. Si lo apaga antes de que se complete el proceso, la animación finalizará. Ahora también puede agregar una función de devolución de llamada. Puede escribir los operadores
doOnAnimationReady ,
doOnAnimationStart ,
doOnAnimationEnd y similares:
fun fadeIn(view:View) : AnimationCompletable { return AnimationBuilder.forView(view) .alpha(1f) .duration(2000L) .buildCompletable() .doOnAnimationReady { view.alpha = 0f } }
En este ejemplo, mostramos lo conveniente que es usar
AnimationBuilder y cambiamos el estado de nuestra Vista antes de comenzar la animación.
Informar video
Analizamos una de las opciones para crear, componer y ajustar animaciones usando Kotlin y RxJava. Aquí hay un enlace a un
proyecto que describe animaciones básicas y ejemplos para ellos, así como los shells principales para trabajar con animaciones.
Además del descifrado, comparto un video del informe:
Altavoces MBLT DEV 2018
Antes de
MBLT DEV 2018 , quedan poco más de dos meses. Tendremos las siguientes actuaciones:
- Laura Morinigo, experta en desarrolladores de Google
- Kaushik Gopal, autor del podcast fragmentado
- Artyom Rudoi, Badoo
- Dina Sidorova, Google y otros .
Mañana el precio del boleto cambiará.
Regístrese hoy