Animations with RxAndroid

In the previous post you saw a brief example about Model View Presenter pattern and how to apply it to update the value of a progress indicator.

In this post you’ll go further and will animate the transition between the initial and final progress values and will include a couple o new functions to your view.

In order to achieve the animation you’ll use a functional-reactive approach through RxAndroid and Retrolambda, so let’s add them to your project.

Include retrolambda at the top of your gradle file as a plugin:

buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath 'me.tatarka:gradle-retrolambda:2.5.0'
    }
}

apply plugin: 'me.tatarka.retrolambda'

Now is the turn of Retrolambda. Add the next lines into your dependencies section:

compile 'io.reactivex:rxandroid:1.0.1'
compile 'io.reactivex:rxjava:1.0.14'

Going reactive

Your objective is create a frame by frame animation between the initial and final progress values. You already know them, so your main task is calculate the middle points and update the indicator each certain time to get the animation effect.

int finalValue = new Random().nextInt(100);
int difference = finalValue - currentProgress;
int direction = difference > 0 ? 1 : -1;

Integer numbers[] = new Integer[Math.abs(difference)];

for(int i = 0; i < Math.abs(difference); i++) {
    numbers[i] = currentProgress + direction * i;
}

First, the difference between the values is calculated and then, you create and array, numbers, that contains the middle values.

Now, define two Observables, one that emits for each value in the numbers array:

Observable<Integer> values = Observable.from(numbers);

And another one that emits values every 30 milliseconds:

Observable<Long> interval = Observable.interval(30, TimeUnit.MILLISECONDS);

The goal is to combine them into an unique observable that emits the correct middle values each X time to achieve the animation effect.

Thanks to RxJava, it’s possible through the Zip operator:

Zip operator

So the code:

Observable.zip(values, interval, (animationValue, aLong) -> animationValue)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(animationValue -> mainView.updateProgress((int)animationValue));

Let’s see operator by operator:

  • zip() combines the two observables and emits the middle values each 30 milliseconds.
  • subscribeOn() indicates that the operations have to be done in a separete thread.
  • observeOn() to receive the result in the main thread. This is useful in order to update the user interface.
  • subscribe() indicates the action to be triggered when a value is observed. In this case, you only update the progress view.

Now if you run the project, you’ll see the animated transition between values every time the button is tapped. However, the button is never disabled, you’re able to tap it multiple times and it produces ugly results, so let’s fix it.

Final touches

You need a mechanisms to disable the button while the animation is running. The first step is include the new functions in the View definition:

public interface MainView {
    void updateProgress(int progress);
    void disableButton();
    void enableButton();
}

The implementation is quite straightfordward

@Override
    public void enableButton() {
        button.setEnabled(true);
    }

    @Override
    public void disableButton() {
        button.setEnabled(false);
    }

At this time, the las step is to include them in the correct place within the presenter:

@Override
public void calculateProgress(int currentProgress) {
    mainView.disableButton();

    // rest of the implementation

    Observable.zip(values, interval, (animationValue, aLong) -> animationValue)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .doOnCompleted(() -> mainView.enableButton())
            .subscribe(animationValue -> mainView.updateProgress((int)animationValue));

Just after tapping the button, it will be disabled. When all the process is over, the operator doOnCompleted() enables the button again.

You can find all the code here.