Android Animations Powered by Kotlin and RXJava2

e-legion
e-Legion
Published in
10 min readAug 10, 2018

Dive into the Android animations and boost your RXJava2 and Kotlin skills with Ivan Škorić from PSPDFKit. He made a talk at the MBLT DEV 2017 conference and now we use this approach on a daily basis in our team at e-Legion ’cause it is really useful.

Animations on Android

There are 4 classes that are used by default on Android framework.

1. ValueAnimator is used for animating values. So you have out-of-the-box methods for animating specific values, mostly primitive values.

2. ObjectAnimator is a subclass of ValueAnimator that allows you to support animation for object properties.

3. AnimatorSet is basically used for scheduling animations. Example of usage in a sequence:

  • View comes on the left side of the screen.
  • After completing the first animation there is a need to perform an appearance animation for another View, etc.

4. ViewPropertyAnimator automatically starts and optimizes animations for the selected property View. This class we will be using most of the time. So we are going to use this ViewPropertyAnimator API and then wrap it in RxJava in reactive.

ValueAnimator

Take a look at ValueAnimator. You can use ValueAnimator.ofFloat that you want to animate float from 0 to 100. You set some Duration and then you start the animation.

Take a look at the example:

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

Here we add UpdateListener and as the value gets updated we will apply that value to the View and move the object from 0 to 100, although this is not a good way to perform this operation.

ObjectAnimator

A better way to do so is ObjectAnimator:

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

Give the command to change the desired View specific parameter to a certain value and set the time by setDuration. The thing is you have to have setTranslationX method in your class because what this will use reflection to get this method and then to actually animate the View. The problem is, it’s using reflection so it’s slower.

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

It is not really that convenient to use it, especially if there are a lot of things and you want to do some complex stuff, meaning you want to have some delays between animations. The more things you want to do the harder it gets to control it.

ViewPropertyAnimator

If you ever did animations on Android this is kind of go-to class for animating views. It provides a great API to schedule the animations you want to run.

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

Run the method ViewCompat.animate that returns a ViewPropertyAnimator, and to animate translationX set the value to 50, translationY parameter — 100. Then you can declare duration of the animation, the interpolator you are going to use. Interpolator defines like in what way the animation is going to run. This example uses an interpolator that goes faster in the beginning and slows in the end. We can declare some start delay for the animation.

In addition, we have AnimatorListener. With this listener, you can listen to specific events happening during the animation runtime. This interface has 4 methods: onAnimationStart, onAnimationCancel, onAnimationEnd, onAnimationRepeat.

We will usually just care about the end of action. In API 16 it was decided to introduce withEndAction:

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

With end action, you can define Runnable and something will run when this animation is finished.

The comments about the process of creating the animation as a whole:

1. start() method is optional. as soon as you define ViewPropertyAnimator the animations are going to get scheduled.

2. Only one animator can animate a specified view at a particular time. Which means just if you are animating a View, one animator can perform on it. If you want to do multiple animations like I want to move something but also want to expand it, you have to define it in one animator. You cannot just define two of them and then apply them at the same time because just one is affecting single View.

Why RxJava?

Let’s start with a simple example. Assume we create a method 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()
}
}
}

This is a fairly primitive solution, and to apply it to your project, you will need to take into account some peculiarities.

We are going to create a CompletableSubject that we’ll use to wait for the animation to complete, and then use the onComplete method to send messages to subscribers. To start the animation sequentially, you need to start the animation not immediately, but as soon as someone subscribes to it. This way, you can run multiple animations in a reactive style sequentially.

Consider the animation itself. In it, we transfer the View over which the animation will be performed, and specify the duration of the animation. And since this is an appearance animation, we have to specify transparency 1.

Let’s create a simple animation using what is written. We have four buttons. And what we want to do is animate them, fade them in with duration being 1000 milliseconds or one second:

val durationMs = 1000Lbutton1.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()

As a result, we have a simple syntax executes. We can use the andThen operator to run animations sequentially. When we subscribe to it, it will send the doonSubscribe event to Completable, which is the first in line for execution. After its completion, it will be signed to the second, third, and so on in the chain. Therefore, if an error occurs at some stage, the entire sequence produces an error. You also need to specify alpha 0 because we want the buttons to be invisible first. And here’s how it would look.

In Kotlin we can make extension method:

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

We apply fadeIn on the view so we can make it extension of view class by defining this. We say View.fadeIn and then we just need duration. And we say ViewCompat.animate(this) because the object we are going to perform a method on is now referred to as this we can get it from this.

This is what Kotlin can do. Let’s see how the call to this function has changed in the animations:

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

Pretty nice syntax. We can basically read what it is going to do. FadeIn button 1 andThen fadeIn button 2 and fadeIn button 3 and so on.

Here we need to specify the duration each time we want to execute and every time it was one thousand. These are like default parameter values in Kotlin as well. So we can say the default execution type for animation is 1 second. And now we can remove this duration and each of these animations will execute in 1 second.

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

If there is a need to have button 2 execute in two seconds, we can specify specifically for that button. Other buttons will appear in 1 second.

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

Sequential execution

We were able to run a sequence from the animation using the andThen statement. What if I need to execute 2 animations simultaneously? There is an operator mergeWith, which will merge Completable as their past so there will all be run at the same time and the last one the callsonComplete then the whole thing will callonComplete. It runs all of them and it finishes once the last one finishes. If we change andThen to mergeWith, we will get an animation in which all buttons appear simultaneously, but the button 2 will appear a little longer than the others:

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

We also can group the animations. For example, you want to fade in first two buttons together then you want to fade in third and fourth button.

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

Combine the first and second buttons with the mergeWith, repeat the action for the third and fourth, and run these groups sequentially using the andThen operator. Now let’s improve the code by adding the fadeInTogether method:

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

It allows you to run the fadeIn animation for two Views at the same time. How did the animation chain changes:

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

As a result:

Take a look at a more complex example. Let’s say we want to show an animation with some set delay. The interval will help:

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

It will generate values every 100 milliseconds. Each button will appear after 100 milliseconds. Next, specify another Observable, which will issue buttons. In this case, we have 4 buttons. Let’s use the zip.

We have event streams in front of us:

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

The string that we have up here is the first one the timeObservable. It will emit numbers in certain time frames. Let’s imagine this is 100 milliseconds.

And the second button observable will emit our Views and it is going to meet them right away together. Zip waits for one object to come from the first string and takes one object from the second stream, and then merges them together. Even though all these four will be available right away it will wait for the first stream to send their object. So you will merge first object from this one with the first view and then 100 milliseconds later when the second comes in it will merge with the second so you basically get our views coming in certain time intervals.

Let’s define what is called BiFunction in RxJava. The function takes two objects and returns the third one. We want to take time and we want to take view.and get disposable due to the fact that we trigger the FadeIn animation and subscribe to it. The value of time does not matter, so we get:

The Library or Project Name

Talk about a project that Ivan began to develop MBLT DEV 2017. He wanted to make the whole system more typified.

It presents a variety of skins for animation. We have already considered this above. It also contains ready-made animations that you can use. There is a generalized set of tools to create your own animations. This library will provide you with more powerful components for reactive programming.

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

Let’s say we are going to create this fadeIn and now it is not going to return Completable but it is going to return AnimationCompletable. The class AnimationCompletable extended the Completable object so we have more options. One important thing is in our previous implementation we could not cancel the animations. So they would be drawn they would give a feedback but there is no cancel it. Now you can create animation Completable that will actually stop the animation once you unsubscribe from it.

We create the appearing animation using AnimationBuilder is one of the library classes. Specify which View the animation will be applied to. In fact, this class copies the behavior of the ViewPropertyAnimator, but with the difference that the output is a stream.

So you put alpha to 1f, duration to 2 seconds and then build. Now once you call build we are going to get the animation. Animation I define it as the immutable object so it holds parameters for the animation. It is not going to start anything. So it just going to define how the animation is going to look.

Run toCompletable, which will create AnimationCompletable. It will wrap the parameters of the animation in a kind of reactive way, and once you call subscribe — will execute the animation. If you dispose of it before it is finished, the animation will stop. You can also add a callback to it. You can say doOnAnimationReady, doOnAnimationStart, doOnAnimationEnd:

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

In this example, we showed how to use AnimationBuilder conveniently, and changed the state of our View before starting the animation.

Video

We reviewed one of the options how to create layout and animation setting using Kotlin and RxJava. Here is a link to the project that describes the basic animations and examples for them, as well as the basic shell to work with the animation.

Learn more about the development process at Netflix, Badoo, Uber and other top companies

We have a chance to discuss new features at MBLT DEV, occurring on the 28th of September in Moscow. Grab your ticket now: the closer the date the higher the price!

--

--

e-legion
e-Legion

We build apps for businesses and organise events for developers.