Podcast
Videos
September 6, 2022
Nov 2022
8 Min

Lottie-Android - Steigerung der User Experience mit kleinem Aufwand

Wie man mit dem Framework Lottie geniale Animationen mit nur wenigen Zeilen Code in iOS einfügt, kann man bereits in unserem Artikel von letzter Woche nachlesen. Da wir bei Jamit Labs nicht nur Applikationen für iOS, sondern ebenso für Android programmieren, erklären wir in diesem Artikel, wie unser Android Team mit seiner Hilfe die verrücktesten Ideen unserer Designer mit geringem Aufwand umsetzt.

Hinzufügen des Frameworks zum Projekt

Um Lottie einem Android Projekt hinzuzufügen, muss in der build.gradle folgende Zeile
in die Dependencies aufgenommen werden:

...
dependencies {
   ...
   implementation 'com.airbnb.android:lottie:$latest'
   ...
}
...Anschließend muss eine Synchronisation des Projekts durchgeführt werden, sodass die IDE das Framework
herunterlädt und dem Projekt verfügbar macht.

Implementierung

Nachdem der Designer die Animation in Adobe AfterEffects erstellt und exportiert hat, kann diese dem Projekt hinzugefügt werden. Im Android Projekt müssen diese dem assets Ordner hinzugefügt werden, sodass einfach darauf zugegriffen werden kann.

Nun kann eine LottieAnimationView überall im Projekt eingebaut werden, um die in den assets gespeicherten Animationen abzuspielen. Dafür wird ins entsprechende Layout eine spezielle View eingebunden:

...
<com.airbnb.lottie.LottieAnimationView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   app:lottie_fileName="animation.json"
   app:lottie_autoPlay="true" />
...

Es muss lediglich das JSON-File angegeben und das autoPlay Flag gesetzt werden, dann startet die Animation, sobald sie vom Lottie Framework geparst und geladen wurde.

Abfolge von verschiedenen Animationen

So weit ist die Verwendung von Lottie also nur mit wenig Aufwand verbunden, will man allerdings eine Abfolge von einzelnen JSON-Files wie in folgendem Beispiel abspielen, muss man doch etwas mehr Aufwand investieren.

Wir bei Jamit Labs arbeiten in unseren Android Projekten mit Databinding und der MVVM-Architektur.
In unseren benutzerdefinierten BindingAdapter haben wir das folgende Binding hinzugefügt:

...
@BindingAdapter(value = ["animation_file", "start_animation"])
fun setAnimationFileAndStart(lottieAnimationView: LottieAnimationView,
                            animationFile: String?,
                            startAnimation: Boolean?) {

   if ((lottieAnimationView.getTag(R.id.lottie_animation_file) as? String) != animationFile) {
       lottieAnimationView.setAnimation(animationFile)
       lottieAnimationView.setTag(R.id.lottie_animation_file, animationFile)
   }

   if (startAnimation) {
       lottieAnimationView.playAnimation()
   }
}
...

Wir verwenden Lotties eigene Attribute lottie_file und lottie_autoPlay nicht, sondern nutzen das obige Binding, um die Daten flexibel aus dem ViewModel laden zu können. Da beispielsweise bei jedem Orientation Change die Activity neu erstellt und somit auch jedes Binding erneut ausgeführt wird, wird die Überprüfung mit dem Tag der lottieAnimationView benötigt. Andernfalls würde die Animation jedes Mal neu geladen und entsprechend von vorne gestartet werden. Ist der Tag jedoch unterschiedlich, das heißt wir wollen das nächste JSON-File laden, so wird dies mit lottieAnimationView.setAnimation(animation_file) getan. Anschließend muss der Tag noch mittels lottieAnimationView.setTag(R.id.lottie_animation_file, animationFile) neu gesetzt werden, sodass obiges Verhalten gewährleistet werden kann.

Um die JSON-Files austauschen zu können, müssen wir nun noch auf die verschiedenen Events der LottieAnimationView reagieren. Dies wird mit folgendem Binding ermöglicht:

...

@BindingAdapter("animation_model")
fun bindAnimationModel(lottieAnimationView: LottieAnimationView, model: AnimationModel?) {
   if (model != null) {
       lottieAnimationView.addAnimatorListener(
               object : Animator.AnimatorListener {
                   override fun onAnimationRepeat(p0: Animator?) {
                   }

                   override fun onAnimationEnd(p0: Animator?) {
                       model.hasEnded.onNext(true)
                   }

                   override fun onAnimationCancel(p0: Animator?) {
                   }

                   override fun onAnimationStart(p0: Animator?) {
                   }
               })
   }
}
...

Um über Events wie das Beenden einer Animation benachrichtigt zu werden, nutzen wir ein AnimationModel, welches die oben beschriebenen Funktionen beinhaltet. Nun müssen wir der LottieAnimationView einen AnimationListener hinzufügen, welcher unser model über die einzelnen Events informiert.

Das AnimationModel sieht dabei wie folgt aus:

class AnimationModel {
   val hasEnded: BehaviorSubject<Boolean> = BehaviorSubject.create()
}

In unserem ViewModel müssen wir entsprechend reagieren können, sodass die JSON-Files ausgetauscht werden können.

...
var animationListener: Disposable? = null
...

fun onInit() {
   animationListener = animationModel.hasEnded.subscribe({
       // Replace current JSON-File
       animationFile.set("other_animation_file.json")
   })
}
...
override fun onFinish() {
   super.onFinish()
   animationListener?.dispose()
}
...

Hierzu benötigen wir einen animationListener welcher sich bei Initialisierung von dem ViewModel auf das hasEnded Event des AnimationModels registriert. Wird nun hasEnded über das Binding aufgerufen, kann das JSON-File ausgetauscht werden. Der animationListener muss schließlich in onFinish wieder disposed werden, sodass keine Leaks und Seiteneffekte auftreten können.

Zudem verändert sich die Verwendung der LottieAnimationView ein wenig, so dass aus obigem Layoutelement folgendes wird:

...
<com.airbnb.lottie.LottieAnimationView
   android:id="@+id/animation_enter"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   app:animation_file="@{viewModel.animationFile}"
   app:animation_model="@{viewModel.animationModel}"
   app:start_animation="@{true}" />
...

Es werden nun nicht mehr die Attribute von Lottie verwendet, sondern die die im BindingAdapter definiert wurden. Wir sehen auch, dass wir das JSON-File nun nicht mehr als String Ressource hier im Layout angeben, sondern über das ViewModel gehen, da sich dies zur Laufzeit verändern kann.

Anmerkungen

Um diese Bindings so verwenden zu können, wie sie oben beschrieben sind, muss Lottie in Version ab
2.5.0-RC2 dem Projekt hinzugefügt werden.

Weiterhin ist zu beachten, dass bei größeren JSON-Files die Ladezeit der LottieAnimationView nicht zu vernachlässigen ist. Gerade der Wechsel zwischen den JSON-Files kann so die User Experience stark einschränken. Um das zu umgehen, können Animationen vorgeladen werden. Wir verwenden dazu das folgende Binding, das über Reflection Lotties internen Cache füllt:

...

/**
* This binding can be used to preload additional lottie files when they should be played in sequence
* Do not include the animation that is played initially in the animationFiles list of this method!
* This would cause the animation to be loaded twice
*/
@Suppress("UNCHECKED_CAST")
@BindingAdapter("preload_animations")
fun preloadLottieAnimationFiles(lottieAnimationView: LottieAnimationView,
                               animationFiles: Collection<String>) {

   animationFiles.forEach { animationName ->
       // currently the Lottie API does not provide a public method to preload files. This is a copy of what's
       // happening within setAnimation in LottieAnimationView, just without setting the actual composition
       // Since the cache field is private, make it accessible first using reflection
       LottieComposition.Factory.fromAssetFileName(lottieAnimationView.context, animationName) { composition ->
           val refCacheField = LottieAnimationView::class.java.getDeclaredField("ASSET_WEAK_REF_CACHE")
           refCacheField.isAccessible = true
           val refCacheMap = (refCacheField.get(null) as MutableMap<String, WeakReference<LottieComposition?>>)
           refCacheMap[animationName] = WeakReference(composition)
       }
   }
}
...

Um dieses Binding zu verwenden, ändert sich die Verwendung der LottieAnimationView erneut:

...
<com.airbnb.lottie.LottieAnimationView
   android:id="@+id/animation_enter"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   app:animation_file="@{viewModel.animationFile}"
   app:animation_model="@{viewModel.animationModel}"
   app:preload_animations="@{viewModel.preloadAnimationFiles}"
   app:start_animation="@{true}" />
...

Im ViewModel wird nun folgende Konstante hinzugefügt:

val preloadAnimationFiles = listOf("Name of further animation files")

Hier kann man alle aufeinanderfolgenden JSON-Files auflisten. Zu beachten ist, dass das erste JSON-File in dieser Liste nicht enthalten sein sollte, da ansonsten diese Animation zweimal deserialisiert würde.

Ausblick

Wie Lottie auf ihrer Github Seite schreibt:

"They say a picture is worth 1,000 words so here are 13,000",

können Animationen die User-Experience deutlich steigern. Mit den hier gezeigten Erkenntnissen sind wir nun gewappnet, um jegliche Animationen, die sich unsere Designer ausdenken, in kürzester Zeit umzusetzen und der App das gewisse Etwas zu verleihen.

Andreas Link
Andreas Link
Anh Dung Pham
Anh Dung Pham
Cihat Gündüz
Cihat Gündüz
Andreas Link
Ekrem Sentürk
Eva Maria Stock
Eva-Marie Stock
Andreas Link
Giulia Maier
Inken Marei Kolthoff
Inken Marei Kolthoff
Janina Baumann
Janina Baumann
Janina Bokeloh
Janina Bokeloh
Jeanette Schmidt
Jeanette Schmidt
Jens Krug
Jens Krug
Kajorn Pathomkeerati
Kajorn Pathomkeerati
Karl Barth
Karl Barth
Kay Dollt
Kay Dollt
Murat Yilmaz
Murat Yilmaz
Thorsten Hack
Thorsten Hack
Thorsten Hack
Thorsten Hack
Inken Marei Kolthoff
Cynthia Murat
Inhaltsverzeichnis

Weitere Artikel

Android - Bilder für unterschiedliche Pixeldichten anlegen
Inken Marei Kolthoff
26.11.2022
3 Min

Android - Bilder für unterschiedliche Pixeldichten anlegen

Android läuft auf den unterschiedlichsten Geräten, deren Screengrößen und Pixeldichten stark variieren.

Artikel lesen
Swift Challenge Mai 2017 - Auflösung
Inken Marei Kolthoff
26.11.2022
1 Min

Swift Challenge Mai 2017 - Auflösung

Für alle, die bei der Challenge mitgemacht haben, haben wir hier noch die passende Auflösung:

Artikel lesen
Arbeiten, Lernen, Pause Machen
Eva-Maria Stock
26.11.2022
4 Min.

Arbeiten, Lernen, Pause Machen

Wie so oft, wollen wir euch einmal mehr an unserer alltäglichen Arbeitswelt teilhaben lassen diesmal etwas weniger alltäglich als eher allmonatlich:

Artikel lesen

Jetzt kostenloses Strategiegespräch sichern!

Die Beratungen sind grundsätzlich schnell ausgebucht, deshalb fülle jetzt in 2 Minuten das kurze Formular aus.

Jetzt Strategiegespräch sichern