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 AnimationModel
s 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.