Android-Animationen basierend auf Kotlin und RxJava



Hallo Habr! Im vergangenen Jahr sprach Ivan Škorić von PSPDFKit bei MBLT DEV mit einem Bericht über das Erstellen von Animationen in Android basierend auf Kotlin und der RxJava-Bibliothek.

Ich verwende jetzt die Techniken aus dem Bericht bei der Arbeit an meinem Projekt, sie helfen sehr. Unter der Zwischensequenz befindet sich die Abschrift des Berichts und des Videos. Jetzt können Sie diese Tricks nutzen.

Animation


In Android gibt es 4 Klassen, die wie standardmäßig angewendet werden:

  1. ValueAnimator - Diese Klasse bietet einen einfachen Synchronisationsmechanismus zum Ausführen von Animationen, mit denen animierte Werte berechnet und für die Ansicht festgelegt werden.
  2. ObjectAnimator ist eine Unterklasse von ValueAnimator, mit der Sie Animationen für Objekteigenschaften unterstützen können.
  3. AnimatorSet wird verwendet, um eine Animationssequenz zu erstellen. Sie haben beispielsweise eine Folge von Animationen:

    1. Ansichtsblätter auf der linken Seite des Bildschirms.
    2. Nach Abschluss der ersten Animation möchten wir eine Erscheinungsanimation für eine andere Ansicht usw. ausführen.
  4. ViewPropertyAnimator - Startet und optimiert automatisch Animationen für die ausgewählte View-Eigenschaft. Wir werden es hauptsächlich benutzen. Daher werden wir diese API verwenden und sie dann im Rahmen der reaktiven Programmierung in RxJava einfügen.


ValueAnimator


Lassen Sie uns das ValueAnimator- Framework analysieren . Es wird verwendet, um den Wert zu ändern. Sie legen den Wertebereich über ValueAnimator.ofFloat für den primitiven Float-Typ von 0 bis 100 fest. Geben Sie den Wert für die Dauer an und starten Sie die Animation.
Betrachten Sie ein Beispiel:

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

Hier fügen wir den UpdateListener hinzu und verschieben mit jedem Update unsere Ansicht horizontal und ändern ihre Position von 0 auf 100, obwohl dies keine sehr gute Möglichkeit ist, diesen Vorgang auszuführen.

ObjectAnimator


Ein weiteres Beispiel für die Implementierung einer Animation ist ObjectAnimator:

 val objectAnimator = ObjectAnimator.ofFloat(textView, "translationX", 100f) objectAnimator.duration = 1000 objectAnimator.start() 

Wir geben ihm den Befehl, den spezifischen View-Parameter auf den angegebenen Wert in die gewünschte View zu ändern und die Zeit mit der setDuration- Methode einzustellen . Die Quintessenz ist, dass Ihre Klasse die setTranslationX- Methode haben muss, dann wird das System diese Methode durch Reflexion finden und dann wird die Ansicht animiert. Das Problem ist, dass hier Reflexion verwendet wird.

Animatorset


Betrachten Sie nun die AnimatorSet- Klasse:

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

In der Tat ist es nicht sehr bequem zu verwenden, insbesondere für eine große Anzahl von Objekten. Wenn Sie komplexere Animationen erstellen möchten, legen Sie beispielsweise eine Verzögerung zwischen dem Erscheinen von Animationen fest. Je mehr Animationen Sie ausführen möchten, desto schwieriger ist die Steuerung.

ViewPropertyAnimator


Die letzte Klasse ist ViewPropertyAnimator . Es ist eine der besten Klassen zum Animieren von View. Dies ist eine großartige API zum Einführen einer Folge von Animationen, die Sie ausführen:

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

Wir starten die ViewCompat.animate- Methode, die ViewPropertyAnimator zurückgibt , und setzen für die Animation übersetzenX den Wert auf 50, für den Parameter translatonY auf 100. Dann geben wir die Dauer der Animation sowie den Interpolator an. Der Interpolator bestimmt die Reihenfolge, in der Animationen angezeigt werden. In diesem Beispiel wird ein Interpolator verwendet, der den Start der Animation beschleunigt und am Ende eine Verlangsamung hinzufügt. Wir fügen auch eine Verzögerung hinzu, um die Animation zu starten. Zusätzlich haben wir einen AnimatorListener . Damit können Sie bestimmte Ereignisse abonnieren, die während der Animation auftreten. Diese Schnittstelle verfügt über 4 Methoden: onAnimationStart , onAnimationCancel , onAnimationEnd , onAnimationRepeat .

In der Regel sind wir nur daran interessiert, die Animation zu vervollständigen. In API Level 16
hinzugefügt mitEndAction:

 .withEndAction({ //API 16+ //do something here where animation ends }) 

Darin können Sie die Runnable- Oberfläche definieren. Nach Abschluss einer bestimmten Animation wird eine Aktion ausgeführt.

Nun ein paar Kommentare zum Prozess der Erstellung von Animationen im Allgemeinen:

  1. Die start () -Methode ist optional: Sobald Sie die animate () -Methode aufrufen, wird eine Folge von Animationen eingeführt. Wenn der ViewPropertyAnimator konfiguriert ist, startet das System die Animation, sobald es dazu bereit ist.
  2. Nur eine ViewPropertyAnimator- Klasse kann nur eine bestimmte Ansicht animieren. Wenn Sie beispielsweise mehrere Animationen ausführen möchten, um etwas zu verschieben und gleichzeitig die Größe zu erhöhen, müssen Sie dies in einem Animator angeben.

Warum haben wir uns für RxJava entschieden?


Beginnen wir mit einem einfachen Beispiel. Angenommen, wir erstellen eine FadeIn-Methode:

 fun fadeIn(view: View, duration: Long): Completable { val animationSubject = CompletableSubject.create() return animationSubject.doOnSubscribe { ViewCompat.animate(view) .setDuration(duration) .alpha(1f) .withEndAction { animationSubject.onComplete() } } } 

Dies ist eine ziemlich primitive Lösung, und um sie auf Ihr Projekt anzuwenden, müssen Sie einige Nuancen berücksichtigen.

Wir werden ein CompletableSubject erstellen, mit dem wir warten, bis die Animationen abgeschlossen sind, und dann die onComplete- Methode verwenden, um Nachrichten an Abonnenten zu senden. Um Animationen nacheinander auszuführen, müssen Sie die Animation nicht sofort starten, sondern sobald jemand sie abonniert. Auf diese Weise können Sie mehrere Animationen im reaktiven Stil nacheinander ausführen.

Betrachten Sie die Animation selbst. Darin übertragen wir die Ansicht, über die die Animation ausgeführt wird, und geben auch die Dauer der Animation an. Und da diese Animation Erscheinungsbild ist, müssen wir Transparenz 1 angeben.

Versuchen wir, unsere Methode zu verwenden und eine einfache Animation zu erstellen. Angenommen, wir haben 4 Schaltflächen auf dem Bildschirm und möchten für sie eine Animation mit einer Dauer von 1 Sekunde hinzufügen:

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

Das Ergebnis ist solch ein prägnanter Code. Mit dem Operator andThen können Sie Animationen nacheinander ausführen. Wenn wir es abonnieren, sendet es das doOnSubscribe- Ereignis an Completable , das das erste in der Ausführungswarteschlange ist. Nach seiner Fertigstellung wird er die zweite, dritte usw. der Kette abonnieren. Wenn daher irgendwann ein Fehler auftritt, löst die gesamte Sequenz einen Fehler aus. Sie müssen vor dem Start der Animation auch Alpha 0 angeben, damit die Schaltflächen nicht sichtbar sind. Und so wird es aussehen:


Mit Kotlin können wir die Erweiterungen verwenden:

 fun View.fadeIn(duration: Long): Completable { val animationSubject = CompletableSubject.create() return animationSubject.doOnSubscribe { ViewCompat.animate(this) .setDuration(duration) .alpha(1f) .withEndAction { animationSubject.onComplete() } } } 

Für die View-Klasse wurde eine Erweiterungsfunktion hinzugefügt. In Zukunft muss das View-Argument nicht mehr an die fadeIn-Methode übergeben werden. Jetzt können Sie alle Aufrufe von View durch das Schlüsselwort this in der Methode ersetzen. Dazu ist Kotlin in der Lage .

Mal sehen, wie sich der Aufruf dieser Funktion in unserer Animationskette geändert hat:

 button1.fadeIn(durationMs) .andThen(button2.fadeIn(durationMs)) .andThen(button3.fadeIn(durationMs)) .andThen(button4.fadeIn(durationMs)) .subscribe() 

Jetzt sieht der Code verständlicher aus. Es wird klargestellt, dass wir eine Animation mit einer bestimmten Dauer auf die gewünschte Anzeige anwenden möchten. Mit dem Operator andThen erstellen wir eine sequentielle Kette von Animationen für die zweite, dritte Schaltfläche usw.

Wir geben immer die Dauer der Animationen an, dieser Wert ist für alle Anzeigen gleich - 1000 Millisekunden. Kotlin kommt wieder zur Rettung. Wir können einen Standardzeitwert festlegen.

 fun View.fadeIn(duration: Long = 1000L): 

Wenn Sie den Parameter dauer nicht angeben, wird die Zeit automatisch auf 1 Sekunde eingestellt. Wenn wir jedoch möchten, dass die Schaltfläche bei Nummer 2 diese Zeit auf 2 Sekunden erhöht, geben wir diesen Wert einfach in der Methode an:

 button1.fadeIn() .andThen(button2.fadeIn(duration = 2000L)) .andThen(button3.fadeIn()) .andThen(button4.fadeIn()) .subscribe() 

Ausführen von zwei Animationen


Mit dem Operator andThen konnten wir eine Folge von Animationen ausführen . Was ist, wenn ich 2 Animationen gleichzeitig ausführen muss? Zu diesem Zweck gibt es in RxJava einen mergeWith- Operator, mit dem Sie Completable- Elemente so kombinieren können, dass sie gleichzeitig gestartet werden. Diese Anweisung startet alle Elemente und endet, nachdem das letzte Element angezeigt wurde. Wenn wir ändern und dann zusammenführen , erhalten wir eine Animation, in der alle Schaltflächen gleichzeitig angezeigt werden , Schaltfläche 2 jedoch etwas länger als die anderen:

 button1.fadeIn() .mergeWith(button2.fadeIn(2000)) .mergeWith(button3.fadeIn()) .mergeWith(button4.fadeIn()) .subscribe() 


Jetzt können wir Animationen gruppieren. Versuchen wir, die Aufgabe zu verkomplizieren: Wir möchten beispielsweise, dass die Schaltflächen 1 und 2 gleichzeitig angezeigt werden und dann die Schaltflächen 3 und 4:

 (button1.fadeIn().mergeWith(button2.fadeIn())) .andThen(button3.fadeIn().mergeWith(button4.fadeIn())) .subscribe() 

Wir kombinieren die erste und zweite Schaltfläche mit dem Operator mergeWith , wiederholen die Aktion für die dritte und vierte und starten diese Gruppen nacheinander mit dem Operator andThen . Jetzt verbessern wir den Code durch Hinzufügen der fadeInTogether- Methode:

 fun fadeInTogether(first: View, second: View): Completable { return first.fadeIn() .mergeWith(second.fadeIn()) } 

Damit können Sie die FadeIn-Animation für zwei Ansichten gleichzeitig ausführen. Wie sich die Animationskette verändert hat:

 fadeInTogether(button1, button2) .andThen(fadeInTogether(button3, button4)) .subscribe() 

Das Ergebnis ist die folgende Animation:


Betrachten Sie ein komplexeres Beispiel. Angenommen, wir müssen eine Animation mit einer bestimmten Verzögerung anzeigen. Die Intervallanweisung hilft:

 fun animate() { val timeObservable = Observable.interval(100, TimeUnit.MILLISECONDS) val btnObservable = Observable.just(button1, button2, button3, button4) } 

Alle 100 Millisekunden werden Werte generiert. Jede Schaltfläche wird nach 100 Millisekunden angezeigt. Als nächstes geben wir ein anderes Observable an, das Schaltflächen ausgibt. In diesem Fall haben wir 4 Tasten. Wir verwenden den Zip- Operator.

Bild

Vor uns liegen die Ereignisse:

 Observable.zip(timeObservable, btnObservable, BiFunction<Long, View, Disposable> { _, button -> button.fadeIn().subscribe() }) 

Der erste entspricht timeObservable . Dieses Observable generiert in regelmäßigen Abständen Zahlen. Angenommen, es sind 100 Millisekunden.

Das zweite Observable generiert eine Ansicht. Der Zip- Operator wartet, bis das erste Objekt im ersten Thread angezeigt wird, und verbindet es mit dem ersten Objekt aus dem zweiten Thread. Trotz der Tatsache, dass alle diese 4 Objekte im zweiten Thread sofort erscheinen, wartet er, bis die Objekte im ersten Thread erscheinen. Somit wird das erste Objekt aus dem ersten Stream in Form unserer Ansicht mit dem ersten Objekt aus dem zweiten Objekt verbunden, und 100 Millisekunden später, wenn ein neues Objekt angezeigt wird, kombiniert der Bediener es mit dem zweiten Objekt. Daher wird die Ansicht mit einer gewissen Verzögerung angezeigt.

Beschäftigen wir uns mit BiFinction in RxJava . Diese Funktion empfängt zwei Objekte als Eingabe, führt einige Operationen an ihnen aus und gibt ein drittes Objekt zurück. Wir möchten uns Zeit nehmen und Objekte anzeigen und verfügbar machen, da wir die fadeIn- Animation aufrufen und abonnieren, um zu abonnieren . Der Wert der Zeit ist uns nicht wichtig. Als Ergebnis erhalten wir diese Animation:


Vanogogh


Ich erzähle Ihnen von dem Projekt , das Ivan für MBLT DEV 2017 entwickelt hat.

Die von Ivan entwickelte Bibliothek präsentiert verschiedene Muscheln für Animationen. Wir haben dies bereits oben berücksichtigt. Es enthält auch vorgefertigte Animationen, die Sie verwenden können. Sie erhalten eine Reihe allgemeiner Tools zum Erstellen eigener Animationen. Diese Bibliothek bietet Ihnen leistungsfähigere Komponenten für die reaktive Programmierung.

Betrachten Sie die Bibliothek anhand eines Beispiels:

 fun fadeIn(view:View) : AnimationCompletable { return AnimationBuilder.forView(view) .alpha(1f) .duration(2000L) .build().toCompletable() } 

Angenommen, Sie möchten eine angezeigte Animation erstellen, diesmal wird jedoch anstelle des Completable- Objekts eine AnimationCompletable angezeigt. Diese Klasse erbt von Completable , sodass jetzt mehr Funktionen angezeigt werden. Ein wichtiges Merkmal des vorherigen Codes war, dass es unmöglich war, Animationen abzubrechen. Jetzt können Sie ein AnimationCompletable- Objekt erstellen, mit dem die Animation gestoppt wird, sobald wir uns abmelden.

Erstellen Sie eine neue Animation mit AnimationBuilder - einer der Bibliotheksklassen. Geben Sie an, auf welche Ansicht die Animation angewendet werden soll. Im Wesentlichen kopiert diese Klasse das Verhalten von ViewPropertyAnimator , jedoch mit dem Unterschied, dass die Ausgabe ein Stream ist.

Als nächstes setzen Sie Alpha 1f und die Dauer beträgt 2 Sekunden. Dann sammeln wir die Animation. Sobald wir die Build- Anweisung aufrufen, wird eine Animation angezeigt. Wir weisen Animationen die Eigenschaft eines unveränderlichen Objekts zu, damit diese Eigenschaften für den Start gespeichert werden. Die Animation selbst wird jedoch nicht gestartet.

Rufen Sie toCompletable auf , um eine AnimationCompletable zu erstellen. Die Parameter dieser Animation werden in eine Art Shell für die reaktive Programmierung eingeschlossen, und sobald Sie sie abonnieren, wird die Animation gestartet. Wenn Sie es ausschalten, bevor der Vorgang abgeschlossen ist, wird die Animation beendet. Sie können jetzt auch eine Rückruffunktion hinzufügen. Sie können die Operatoren doOnAnimationReady , doOnAnimationStart , doOnAnimationEnd und dergleichen schreiben:

 fun fadeIn(view:View) : AnimationCompletable { return AnimationBuilder.forView(view) .alpha(1f) .duration(2000L) .buildCompletable() .doOnAnimationReady { view.alpha = 0f } } 

In diesem Beispiel haben wir gezeigt, wie bequem es ist, AnimationBuilder zu verwenden, und den Status unserer Ansicht geändert, bevor die Animation gestartet wird.

Video melden


Wir haben uns eine der Optionen zum Erstellen, Komponieren und Optimieren von Animationen mit Kotlin und RxJava angesehen. Hier ist ein Link zu einem Projekt , das grundlegende Animationen und Beispiele für diese sowie die Hauptschalen für die Arbeit mit Animationen beschreibt.

Zusätzlich zur Entschlüsselung teile ich ein Video des Berichts:


Referenten MBLT DEV 2018


Vor MBLT DEV 2018 verbleiben noch etwas mehr als zwei Monate. Wir werden folgende Aufführungen haben:

  • Laura Morinigo, Google Entwickler-Expertin
  • Kaushik Gopal, Autor des fragmentierten Podcasts
  • Artyom Rudoi, Badoo
  • Dina Sidorova, Google und andere .

Morgen wird sich der Ticketpreis ändern. Registrieren Sie sich noch heute.

Source: https://habr.com/ru/post/de418383/


All Articles