
Bonjour, Habr! L'annĂ©e dernière, Ivan Ĺ korić de PSPDFKit a parlĂ© Ă
MBLT DEV avec un rapport sur la création d'animations dans Android basé sur Kotlin et la bibliothèque RxJava.
J'utilise maintenant les techniques du rapport pour travailler sur mon projet, elles m'aident beaucoup. Sous la cinématique se trouve la transcription du rapport et de la vidéo, vous pouvez maintenant profiter de ces astuces.
L'animation
Sous Android, il existe 4 classes qui s'appliquent comme par défaut:
- ValueAnimator - Cette classe fournit un mécanisme de synchronisation simple pour exécuter des animations qui calculent des valeurs animées et les définissent pour la vue.
- ObjectAnimator est une sous-classe de ValueAnimator qui vous permet de prendre en charge des animations pour les propriétés d'objet.
- AnimatorSet est utilisé pour créer une séquence d'animation. Par exemple, vous avez une séquence d'animations:
- Affichez les feuilles à gauche de l'écran.
- Après avoir terminé la première animation, nous voulons effectuer une animation d'apparence pour une autre vue, etc.
- ViewPropertyAnimator - Lance et optimise automatiquement les animations pour la propriété View sélectionnée. Nous l'utiliserons principalement. Par conséquent, nous utiliserons cette API et la placerons ensuite dans RxJava dans le cadre d'une programmation réactive.
ValueAnimator
Analysons le cadre
ValueAnimator . Il est utilisé pour modifier la valeur. Vous spécifiez une plage de valeurs via
ValueAnimator.ofFloat pour le type de flottant primitif de 0 à 100. Spécifiez une valeur de
durée et démarrez l'animation.
Prenons un exemple:
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 } })
Ici, nous ajoutons
UpdateListener et à chaque mise à jour, nous déplacerons notre vue horizontalement et changerons sa position de 0 à 100, bien que ce ne soit pas un très bon moyen d'effectuer cette opération.
ObjectAnimator
Un autre exemple d'implémentation d'animation est ObjectAnimator:
val objectAnimator = ObjectAnimator.ofFloat(textView, "translationX", 100f) objectAnimator.duration = 1000 objectAnimator.start()
Nous lui donnons une commande pour changer le paramètre View spécifique à la valeur spécifiée à la vue souhaitée et définir l'heure en utilisant la méthode
setDuration . L'
essentiel est que votre classe doit avoir la méthode
setTranslationX , puis le système trouvera cette méthode par réflexion, puis la vue sera animée. Le problème est que la réflexion est utilisée ici.
Animatorset
Considérez maintenant la 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()
En fait, il n'est pas très pratique à utiliser, notamment pour un grand nombre d'objets. Si vous souhaitez créer des animations plus complexes - par exemple, définissez un délai entre l'apparition des animations et plus vous souhaitez exécuter d'animations, plus il est difficile de le contrôler.
ViewPropertyAnimator
La dernière classe est
ViewPropertyAnimator . C'est l'une des meilleures classes pour animer View. Il s'agit d'une excellente API pour introduire une séquence d'animations que vous exécutez:
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) {} })
Nous commençons la méthode
ViewCompat.animate , qui retourne
ViewPropertyAnimator , et pour la
traduction d' animationX,
nous définissons la valeur à 50, pour le paramètre
translatonY à 100. Ensuite, nous spécifions la durée de l'animation, ainsi que l'interpolateur. L'interpolateur détermine la séquence dans laquelle les animations apparaissent. Cet exemple utilise un interpolateur qui accélère le début de l'animation et ajoute un ralentissement à la fin. Nous ajoutons également un délai pour démarrer l'animation. De plus, nous avons un
AnimatorListener . Avec lui, vous pouvez vous abonner à certains événements qui se produisent pendant l'animation. Cette interface a 4 méthodes:
onAnimationStart ,
onAnimationCancel ,
onAnimationEnd ,
onAnimationRepeat .
En règle générale, nous souhaitons uniquement terminer l'animation. Dans l'API niveau 16
ajouté avec EndAction:
.withEndAction({ //API 16+ //do something here where animation ends })
Dans celui-ci, vous pouvez définir l'interface
Runnable , et après avoir terminé de montrer une animation spécifique, une action est effectuée.
Maintenant, quelques commentaires sur le processus de création d'animations en général:
- La méthode start () est facultative: dès que vous appelez la méthode animate () , une séquence d'animations est introduite. Lorsque ViewPropertyAnimator est configuré, le système démarre l'animation dès qu'il est prêt à le faire.
- Une seule classe ViewPropertyAnimator ne peut animer qu'une vue spécifique. Par conséquent, si vous souhaitez effectuer plusieurs animations, par exemple, vous voulez que quelque chose bouge et en même temps augmenter la taille, vous devez le spécifier dans un seul animateur.
Pourquoi avons-nous choisi RxJava?
Commençons par un exemple simple. Supposons que nous créons une méthode 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() } } }
Il s'agit d'une solution assez primitive, et pour l'appliquer Ă votre projet, vous devrez prendre en compte certaines nuances.
Nous allons créer un
CompletableSubject , que nous utiliserons pour attendre la fin des animations, puis utiliser la méthode
onComplete pour envoyer des messages aux abonnés. Pour exécuter des animations de manière séquentielle, vous devez démarrer l'animation non pas immédiatement, mais dès que quelqu'un s'y abonne. De cette façon, plusieurs animations de style réactif peuvent être exécutées séquentiellement.
Considérez l'animation elle-même. Dans ce document, nous transférons la vue, sur laquelle l'animation sera effectuée, et indiquons également la durée de l'animation. Et comme cette animation est l'apparence, il faut indiquer la transparence 1.
Essayons d'utiliser notre méthode et créons une animation simple. Supposons que nous ayons 4 boutons sur l'écran, et nous voulons ajouter pour eux une animation de l'apparition d'une durée de 1 seconde:
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()
Le résultat est un tel code concis. À l'aide de l'opérateur
andThen ,
vous pouvez exécuter des animations de manière séquentielle. Lorsque nous nous y
abonnons , il enverra l'événement
doOnSubscribe Ă
Completable , qui est le premier de la file d'attente d'exécution. Après son achèvement, il souscrira aux deuxième, troisième et ainsi de suite de la chaîne. Par conséquent, si une erreur se produit à un certain stade, la séquence entière génère une erreur. Vous devez également spécifier alpha 0 avant le début de l'animation afin que les boutons soient invisibles. Et voici à quoi cela ressemblera:
En utilisant
Kotlin , nous pouvons utiliser les extensions:
fun View.fadeIn(duration: Long): Completable { val animationSubject = CompletableSubject.create() return animationSubject.doOnSubscribe { ViewCompat.animate(this) .setDuration(duration) .alpha(1f) .withEndAction { animationSubject.onComplete() } } }
Pour la classe View, une fonction d'extension a été ajoutée. À l'avenir, il n'est pas nécessaire de passer l'argument View à la méthode fadeIn. Vous pouvez maintenant remplacer tous les appels à View par le mot-clé this dans la méthode. C'est ce dont
Kotlin est capable
.Voyons comment l'appel à cette fonction dans notre chaîne d'animation a changé:
button1.fadeIn(durationMs) .andThen(button2.fadeIn(durationMs)) .andThen(button3.fadeIn(durationMs)) .andThen(button4.fadeIn(durationMs)) .subscribe()
Maintenant, le code semble plus compréhensible. Il indique clairement que nous voulons appliquer une animation d'une certaine durée à l'affichage souhaité. En utilisant l'opérateur
andThen ,
nous créons une chaîne séquentielle d'animations vers le deuxième, le troisième bouton, etc.
Nous indiquons toujours la durée des animations, cette valeur est la même pour tous les affichages - 1000 millisecondes.
Kotlin vient à la rescousse. Nous pouvons faire une valeur de temps par défaut.
fun View.fadeIn(duration: Long = 1000L):
Si vous ne spécifiez pas le paramètre de
durée , l'heure sera automatiquement réglée sur 1 seconde. Mais si nous voulons que le bouton au numéro 2 augmente ce temps à 2 secondes, nous spécifions simplement cette valeur dans la méthode:
button1.fadeIn() .andThen(button2.fadeIn(duration = 2000L)) .andThen(button3.fadeIn()) .andThen(button4.fadeIn()) .subscribe()
Exécution de deux animations
Nous avons pu exécuter une séquence d'animations à l'aide de l'opérateur
andThen . Que faire si j'ai besoin d'exécuter 2 animations en même temps? Pour ce faire, il existe un opérateur
mergeWith dans
RxJava qui vous permet de combiner des éléments
Completable de telle manière qu'ils seront lancés simultanément. Cette instruction démarre tous les éléments et se termine après l'affichage du dernier élément. Si nous changeons et
puis pour
fusionner avec , nous obtenons une animation dans laquelle tous les boutons apparaissent en même temps, mais le bouton 2 apparaîtra un peu plus longtemps que les autres:
button1.fadeIn() .mergeWith(button2.fadeIn(2000)) .mergeWith(button3.fadeIn()) .mergeWith(button4.fadeIn()) .subscribe()
Nous pouvons maintenant regrouper des animations. Essayons de compliquer la tâche: par exemple, nous voulons que le bouton 1 et le bouton 2 apparaissent en même temps, puis le bouton 3 et le bouton 4:
(button1.fadeIn().mergeWith(button2.fadeIn())) .andThen(button3.fadeIn().mergeWith(button4.fadeIn())) .subscribe()
Nous combinons les premier et deuxième boutons avec l'opérateur
mergeWith , répétons l'action pour les troisième et quatrième, et démarrons ces groupes séquentiellement avec l'opérateur
andThen . Nous allons maintenant améliorer le code en ajoutant la méthode
fadeInTogether :
fun fadeInTogether(first: View, second: View): Completable { return first.fadeIn() .mergeWith(second.fadeIn()) }
Il vous permettra d'exécuter une animation fadeIn pour deux vues en même temps. Comment la chaîne d'animation a changé:
fadeInTogether(button1, button2) .andThen(fadeInTogether(button3, button4)) .subscribe()
Le résultat est l'animation suivante:
Prenons un exemple plus complexe. Supposons que nous devons montrer une animation avec un certain retard donné. L'instruction d'
intervalle aidera:
fun animate() { val timeObservable = Observable.interval(100, TimeUnit.MILLISECONDS) val btnObservable = Observable.just(button1, button2, button3, button4) }
Il générera des valeurs toutes les 100 millisecondes. Chaque bouton apparaîtra après 100 millisecondes. Ensuite, nous indiquons un autre observable qui émettra des boutons. Dans ce cas, nous avons 4 boutons. Nous utilisons l'opérateur
zip .

Devant nous sont les flux d'événements:
Observable.zip(timeObservable, btnObservable, BiFunction<Long, View, Disposable> { _, button -> button.fadeIn().subscribe() })
Le premier correspond Ă
timeObservable . Cet
observable générera des nombres à intervalles réguliers. Supposons que ce soit 100 millisecondes.
Le second
observable va générer une vue. L'opérateur
zip attend que le premier objet apparaisse dans le premier thread et le connecte au premier objet du deuxième thread. Malgré le fait que tous ces 4 objets du deuxième thread apparaîtront immédiatement, il attendra que les objets commencent à apparaître dans le premier thread. Ainsi, le premier objet du premier flux se connectera au premier objet du second sous la forme de notre vue, et 100 millisecondes plus tard, lorsqu'un nouvel objet apparaît, l'opérateur le combinera avec le deuxième objet. Par conséquent, la vue apparaîtra avec un certain retard.
Traitons de
BiFinction dans
RxJava . Cette fonction reçoit deux objets en entrée, effectue certaines opérations sur eux et renvoie un troisième objet. Nous voulons prendre du temps et voir les objets et devenir
jetables car nous appelons l'animation
fadeIn et nous nous
abonnons pour nous
abonner . La valeur du temps n'est pas importante pour nous. En conséquence, nous obtenons cette animation:
Vanogogh
Je vais vous parler du
projet qu'Ivan a commencé à développer pour MBLT DEV 2017.
La bibliothèque développée par Ivan présente différents coques d'animations. Nous l'avons déjà considéré ci-dessus. Il contient également des animations prêtes à l'emploi que vous pouvez utiliser. Vous obtenez un ensemble généralisé d'outils pour créer vos propres animations. Cette bibliothèque vous fournira des composants plus puissants pour une programmation réactive.
Considérez la bibliothèque en utilisant un exemple:
fun fadeIn(view:View) : AnimationCompletable { return AnimationBuilder.forView(view) .alpha(1f) .duration(2000L) .build().toCompletable() }
Supposons que vous souhaitiez créer une animation qui apparaît, mais cette fois un
AnimationCompletable apparaît à la place de l'objet
Completable . Cette classe hérite de
Completable , donc plus de fonctions apparaissent maintenant. Une caractéristique importante du code précédent était qu'il était impossible d'annuler des animations. Vous pouvez maintenant créer un objet
AnimationCompletable qui arrête l'animation dès que nous nous en désinscrivons.
Créez une animation émergente à l'aide d'
AnimationBuilder - l'une des classes de bibliothèque. Spécifiez la vue à laquelle l'animation sera appliquée. En substance, cette classe copie le comportement de
ViewPropertyAnimator , mais avec la différence que la sortie est un flux.
Ensuite, définissez alpha 1f et la durée est de 2 secondes. Ensuite, nous collectons l'animation. Dès que nous
invoquons l' instruction
build , une animation apparaît. Nous attribuons aux animations la propriété d'un objet immuable, il enregistrera donc ces caractéristiques pour son lancement. Mais l'animation elle-même ne démarre pas.
Appelez
toCompletable , qui créera un
AnimationCompletable . Il enveloppera les paramètres de cette animation dans une sorte de shell pour une programmation réactive, et dès que vous vous y abonnerez, il démarrera l'animation. Si vous le désactivez avant la fin du processus, l'animation se terminera. Vous pouvez également maintenant ajouter une fonction de rappel. Vous pouvez écrire les opérateurs
doOnAnimationReady ,
doOnAnimationStart ,
doOnAnimationEnd, etc .:
fun fadeIn(view:View) : AnimationCompletable { return AnimationBuilder.forView(view) .alpha(1f) .duration(2000L) .buildCompletable() .doOnAnimationReady { view.alpha = 0f } }
Dans cet exemple, nous avons montré à quel point il est pratique d'utiliser
AnimationBuilder et changé l'état de notre vue avant de démarrer l'animation.
Signaler la vidéo
Nous avons examiné l'une des options de création, de composition et de modification d'animations à l'aide de Kotlin et RxJava. Voici un lien vers un
projet qui décrit des animations de base et des exemples pour eux, ainsi que les principaux shells pour travailler avec des animations.
En plus du décryptage, je partage une vidéo du reportage:
Haut-parleurs MBLT DEV 2018
Avant
MBLT DEV 2018 , il reste un peu plus de deux mois. Nous aurons les performances suivantes:
- Laura Morinigo, experte développeur Google
- Kaushik Gopal, auteur du podcast fragmenté
- Artyom Rudoi, Badoo
- Dina Sidorova, Google et autres .
Demain, le prix du billet va changer.
Inscrivez-vous dès aujourd'hui.