Android AnimationDrawable and knowing when animation ends

Viewed 47584

I want to do an animation with several image-files, and for this the AnimationDrawable works very well. However, I need to know when the animation starts and when it ends (i.e add a listener like the Animation.AnimationListener). After having searched for answers, I'm having a bad feeling the AnimationDrawable does not support listeners..

Does anyone know how to create a frame-by-frame image animation with a listener on Android?

14 Answers

I used a recursive function that checks to see if the current frame is the last frame every timeBetweenChecks milliseconds.

private void checkIfAnimationDone(AnimationDrawable anim){
    final AnimationDrawable a = anim;
    int timeBetweenChecks = 300;
    Handler h = new Handler();
    h.postDelayed(new Runnable(){
        public void run(){
            if (a.getCurrent() != a.getFrame(a.getNumberOfFrames() - 1)){
                checkIfAnimationDone(a);
            } else{
                Toast.makeText(getApplicationContext(), "ANIMATION DONE!", Toast.LENGTH_SHORT).show();
            }
        }
    }, timeBetweenChecks);
}

A timer is a bad choice for this because you will get stuck trying to execute in a non UI thread like HowsItStack said. For simple tasks you can just use a handler to call a method at a certain interval. Like this:

handler.postDelayed(runnable, duration of your animation); //Put this where you start your animation

private Handler handler = new Handler();

private Runnable runnable = new Runnable() {

    public void run() {
        handler.removeCallbacks(runnable)
        DoSomethingWhenAnimationEnds();

    }

};

removeCallbacks assures this only executes once.

This is so simple when it come to using Kotlin, AnimationDrawable has two functions we could use to calculate the animation duration, then we could add a runnable with delay to create an Animation listener. here is a simple Kotlin extension.

fun AnimationDrawable.onAnimationFinished(block: () -> Unit) {
    var duration: Long = 0
    for (i in 0..numberOfFrames) {
        duration += getDuration(i)
    }
    Handler().postDelayed({
        block()
    }, duration + 200)
}

You can use registerAnimationCallback to check your animation start and end.

Here's the snippet code:

// ImageExt.kt
fun ImageView.startAnim(block: () -> Unit) {
    (drawable as Animatable).apply {
        registerAnimationCallback(
                drawable,
                object : Animatable2Compat.AnimationCallback() {
                    override fun onAnimationStart(drawable: Drawable?) {
                        block.invoke()
                        isClickable = false
                        isEnabled = false
                    }

                    override fun onAnimationEnd(drawable: Drawable?) {
                        isClickable = true
                        isEnabled = true
                    }
        })
    }.run { start() }
}
// Fragment.kt
imageView.startAnim {
    // do something after the animation ends here
}

The purpose of the ImageExt was to disable after animation start (on progress) to prevent user spamming the animation and resulting in the broken / wrong vector shown.

With frame-by-frame, you might want to trigger another ImageView like this.

// Animation.kt
iv1.startAnim {
    iv2.startAnim {
        // iv3, etc
    }
}

But the above solutions looks ugly. If anyone has a better approach, please comment below, or edit this answer directly.

Related