A story that made it convenient with Kotlin that it is troublesome to execute animation continuously on Android

I had the opportunity to implement a "look like floating" animation on Android. It looks like the following image.

This animation

  1. Move up a little over 2 seconds
  2. Move down a little over 2 seconds

Is realized by executing "continuously" and "repeatedly". "Continuously" means that when the animation in 1. is finished, the animation in 2. is started.

Java-On Android ...

If you do this with Android's View animation API, it's usually terrible code. Next is that.

//Animation that moves up over 2 seconds
final TranslateAnimation anim1 = new TranslateAnimation(
        Animation.RELATIVE_TO_SELF,  0.0f,
        Animation.RELATIVE_TO_SELF,  0.0f,
        Animation.RELATIVE_TO_SELF,  0.0f,
        Animation.RELATIVE_TO_SELF, -0.1f);
anim1.setDuration(2000);

//Animation that moves down over 2 seconds
final TranslateAnimation anim2 = new TranslateAnimation(
        Animation.RELATIVE_TO_SELF,  0.0f,
        Animation.RELATIVE_TO_SELF,  0.0f,
        Animation.RELATIVE_TO_SELF, -0.1f,
        Animation.RELATIVE_TO_SELF,  0.0f);
anim2.setDuration(2000);

anim1.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) { }

    @Override
    public void onAnimationEnd(Animation animation) {
        anim2.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) { }

            @Override
            public void onAnimationEnd(Animation animation) {
                // 3.When the down animation is over, start the up animation again
                view.startAnimation(anim1);
            }

            @Override
            public void onAnimationRepeat(Animation animation) { }
        });

        // 2.When the animation to the top is finished, start the animation to move down
        view.startAnimation(anim2);
    }

    @Override
    public void onAnimationRepeat(Animation animation) { }
});

// 1.Start animation to move up
view.startAnimation(anim1);

The double pain of nesting callbacks is that the process you want to do and the order in which the code is written are reversed, which cannot be done.

This is the only case where you want to use Kotlin (it's better if you use the library that can use both Java and Deferred).

(I didn't even have to use Kotlin ...)

As @glayash commented, if you repeat the animation like this one, you can write it as follows, so it doesn't become a callback hell and you didn't even have to use Kotlin.

//Animation that moves up over 2 seconds
final TranslateAnimation anim1 = new TranslateAnimation(
        Animation.RELATIVE_TO_SELF,  0.0f,
        Animation.RELATIVE_TO_SELF,  0.0f,
        Animation.RELATIVE_TO_SELF,  0.0f,
        Animation.RELATIVE_TO_SELF, -0.1f);
anim1.setDuration(2000);
//Invert and repeat infinitely
anim1.setRepeatMode(Animation.REVERSE);
anim1.setRepeatCount(Animation.INFINITE);
view.startAnimation(anim1);

If this is Kotlin ...

That's why I tried it with Kotlin.

First, create a "function that runs the animation and continues to the next when the animation is finished". Here, I defined it as an extension function of View.

package net.amay077.animsample

import android.view.View
import android.view.animation.Animation
import kotlin.coroutines.experimental.suspendCoroutine

suspend fun View.startAnimationAsync(anim: Animation) {

    return suspendCoroutine { continuation ->
        anim.setAnimationListener(object : Animation.AnimationListener {
            override fun onAnimationStart(animation: Animation?) { }

            override fun onAnimationEnd(animation: Animation?) {
                continuation.resume(Unit)
            }

            override fun onAnimationRepeat(animation: Animation?) { }
        })

        this.startAnimation(anim)
    }
}

The caller looks like this: Heaven compared to Java in callback hell, here ... It seems that you need to use launch (UI) {} instead of ```async () {} `` because the animation needs to be called from the UI thread.

val button1 = findViewById(R.id.button1)

val anim1 = TranslateAnimation(
        Animation.RELATIVE_TO_SELF, 0.0f,
        Animation.RELATIVE_TO_SELF, 0.0f,
        Animation.RELATIVE_TO_SELF, 0.0f,
        Animation.RELATIVE_TO_SELF, -0.5f)
anim1.duration = 2000

val anim2 = TranslateAnimation(
        Animation.RELATIVE_TO_SELF, 0.0f,
        Animation.RELATIVE_TO_SELF, 0.0f,
        Animation.RELATIVE_TO_SELF, -0.5f,
        Animation.RELATIVE_TO_SELF, 0.0f)
anim2.duration = 2000

launch(UI) { //I'll async from the main thread
    //Repeat all the time
    while (true) {
        button1.startAnimationAsync(anim1) // 1.Run an animation that moves up over 2 seconds
        button1.startAnimationAsync(anim2) // 2.Perform an animation that moves down over 2 seconds
    }
}

It's my first time to use Kotlin properly, so maybe I can still improve it. .. Please point out any good code.

For implementation in Kotlin, I referred to the following site

By the way, you can also do it in C

C # (ie Xamarin.Android) can also be achieved with a combination of ʻasync / await (ie Task) and `TaskCompletionSource``.

C # also has an extension method that you can define as follows:

public static class ViewAnimationExtensions
{
    public static Task<bool> StartAnimationAsync(this View view, Animation anim)
    {
        var source = new TaskCompletionSource<bool>();
        EventHandler<Animation.AnimationEndEventArgs> handler = null;

        handler = (sender, e) =>
        {
            anim.AnimationEnd -= handler; //Don't forget to unsubscribe
            source.SetResult(true); //kotlin continuation.resume(Unit)Toko
        };
        anim.AnimationEnd += handler; //Subscribe to the event

        view.StartAnimation(anim);
        return source.Task;
    }
}

This is the start side. Add the ʻawait`` keyword on the call, and add the ʻasync keyword to the method that contains it (here `ʻOnCreate).

protected async override void OnCreate(Bundle savedInstanceState)
{
    /*abridgement*/

    while (true)
    {
        await button1.StartAnimationAsync(anim1);
        await button1.StartAnimationAsync(anim2);
    }
}

It would be nice if Kotlin could be mixed with Java in the same project.

Recommended Posts

A story that made it convenient with Kotlin that it is troublesome to execute animation continuously on Android
Find a value that is convenient to have a method and make it a ValueObject
[Docker] Is it good enough to call it a multi-stage build? → The story that became so good
I made a plugin to execute jextract with Gradle task
A story that made it as easy as possible to check the operation when automatically creating a library update PR with Github Dependabot
Since the Rspec command is troublesome, I tried to make it possible to execute Rspec with one Rake command
A story that I struggled to challenge a competition professional with Java
The story of making it possible to build a project that was built by Maven with Ant
When calling sshpass from Java with shell etc., it seems that it is necessary to have a path.
I made an app to scribble with PencilKit on a PDF file
Created a library that makes it easy to handle Android Shared Prefences
I made a site that summarizes information on carbohydrate restriction with Vue.js
I made a rock-paper-scissors app with kotlin
I made a calculator app on Android
I made a rock-paper-scissors app with android
When is it said that you can use try with a Swift error?
What to do when is invalid because it does not start with a'-'