Android Property Animations: Animator Listeners

Using Animator Listeners

Animator listeners provide a powerful tool to monitor the state of animation. The animation listener implements the classic observer design pattern. The Animator.AnimatorListener interface defines a number of methods that need to be implemented. Any number of listeners can register with an animation. Whenever the state of the animation changes all listeners are notified through calling the respective callback methods.

The AnimatorListener

The Animator.AnimatorListener is an interface that defines four methods.

void onAnimationStart(Animator animation)
void onAnimationRepeat(Animator animation)
void onAnimationEnd(Animator animation)
void onAnimationCancel(Animator animation)

In order to create an instance of an AnimatorListener  you need to implement these four methods. The animator listener is then simply registered with an Animator object by calling the addListener method on the animator.

public void addListener(Animator.AnimatorListener listener)

You can add multiple listeners to a single animation object. You can also add the same listener to multiple animations. The calling animation will be passed as argument to the callbacks.

The names of the callback methods seem pretty self explanatory but some finer details need attention. onAnimationStart is called when the animation actually starts running. This means that it will be called after any start delay has passed. The name might be slightly confusing when comparing to the methods that query the animation status. Assume we have an animation that  has a start delay. When the animation is started with a call to start isStarted will return true before the onAnimationStart method is called. When the start delay has passed the the animation will start running. The onAnimationStart callback is called and isRunning will return true.

onAnimationRepeat will be called whenever an animation is repeated. This happens when the repeat count of the animation is set to a value greater than one with by calling setRepeatCount.

The callback onAnimationCancel is called when the animation is cancelled by calling the cancel method. The callback onAnimationEnd is called whenever the animation ends. This may be naturally, when the animation has completed, or this may be when when the animation is stopped by a call to end or cancel. Note that a call to cancel will cause two callbacks to be invoked, first onAnimationCancel and then onAnimationEnd.

Finally I need to point out a bug (or feature?) in the Android code. When the callback onAnimationEnd is called by an animation, one would expect the animation to have ended. This is essentially true. However, a call to isRunning or isStarted will still return true when issued from within the callback.

To illustrate how the AnimatorListener is used we extend the example of the previous post. In that example we created a method called setStatusTexts which updated the TextView elements displaying the animation status. We then called the method from all the button callbacks. In this example we will remove the calls to setStatusTexts from the button callbacks again. The four methods in the activity now read as follows.

  public void startAnimation(View view) {
    anim.start();
  }

  public void endAnimation(View view) {
    anim.end();
  }

  public void cancelAnimation(View view) {
    anim.cancel();
  }

  public void pauseAnimation(View view) {
    anim.pause();
  }

  public void resumeAnimation(View view) {
    anim.resume();
  }

Instead we will now create an AnimatorListener  that will call the setStatusTexts method. In the onCreate method of the Activity we will now add the following code.

Animator.AnimatorListener animatorListener 
  = new Animator.AnimatorListener() {

    public void onAnimationStart(Animator animation) {
      setStatusTexts();
      messageText.setText("started");
    }

    public void onAnimationRepeat(Animator animation) {
      setStatusTexts();
      messageText.setText("repeating");
    }

    public void onAnimationEnd(Animator animation) {
      setStatusTexts();
      messageText.setText(messageText.getText()+" -- ended");
    }

    public void onAnimationCancel(Animator animation) {
      setStatusTexts();
      messageText.setText("cancelled");
    }
  };

anim.addListener(animatorListener);
The AnimatorListener in action.

The AnimatorListener in action.

We create an anonymous class that implements Animator.AnimatorListener. Each of the four method calls setStatusTexts. In addition we added a text view called messageText. This will simply display when one of the callbacks has been called. When the animation ends we append the message to the previous message. This is done because the onAnimationCancel and onAnimationEnd are called in quick succession when the animation is cancelled. In order to still see that the onAnimationCancel has been called, the ended message is appended. In the last line of the code above, the animator listener is registered with the animation object. The resulting animation can be seen on the right. I have also added a start delay to illustrate when the onAnimationStart method is called. Note how the bug, which I describe above, manifests itself. Even though the animation ends and the callback onAnimationEnd is called, the status texts still indicate that the animation is running.

The AnimatorPauseListener

Since API 19 we can pause and resume animations. To account for this new behaviour a new listener has been added in API 19 called AnimatorPauseListener. It defines two callback methods.

void onAnimationPause(Animator animation)
void onAnimationResume(Animator animation)

These behave in the same way as the callbacks of the AnimatorListener. onAnimationPause is called when the animation is paused and onAnimationResume is called when the animation is resumed. To illustrate, we extend the example further. We create an AnimatorPauseListener in the onCreate method.

Animator.AnimatorPauseListener pauseListener 
  = new Animator.AnimatorPauseListener() {

    public void onAnimationPause(Animator animation) {
      setStatusTexts();
      messageText.setText("paused");
    }

    public void onAnimationResume(Animator animation) {
      setStatusTexts();
      messageText.setText("resumed");
    }
  };

anim.addPauseListener(pauseListener);
The AnimatorPauseListener in action.

The AnimatorPauseListener in action.

Just as before, we now create an anonymous AnimatorPauseListener class and implement the two callback methods. Each of the methods first calls setStatusTexts and then updates the message text to reflect the changes in the animation. The pause listener is registered with the animation through the addPauseListener method. This method was also added in API 19. On the right you can see that this works pretty much as expected. When Pause is pressed the message updates and the isPaused status returns true. When Resume is pressed the message displays the fact and isPaused returns false.

The AnimatorListenerAdapter

In many situation you will only want to implement one of the callback methods of the AnimatorListener or AnimatorPauseListener. In this case it is convenient to use the AnimatorListenerAdapter class. This class implements both the AnimatorListener and the AnimatorPauseListener interfaces and declares empty methods for each of the callbacks. This means that, by default, they will not do anything. You can then override a single method, such as onAnimationEnd and implement some action for that callback. The AnimatorListenerAdapter can be passed to the addListener or the addPauseListener methods.

As always, the code for this tutorial can be found on Github. In the next tutorial on property animations I will talk about the TimeAnimator, what it is and why you would want to use it.

Follow the author

 

6 Comments

  1. Marcin

    Good article but I have an issue with one thing:


    The names of the callback methods seem pretty self explanatory but some finer details need attention. onAnimationStart is called when the animation actually starts running. This means that it will be called after any start delay has passed.

    I’m running KitKat and this is not true, when using AnimatorSet with a start delay, the onAnimationStart() is called immediately after start() which is kind of stupid. It’s either a bug or this behaviour changed between android versions. Any comments?

    Reply
  2. Elizabeth

    Very nice article! I have one question. How do we achieve the pause and resume capabilities on ObjectAnimators for API levels before 19? Any pointers would help.

    Reply
    1. Mikail (Post author)

      The only way that I can think of is to stop an animation and somehow save the current state. On a resume you would then have to create a new animation that starts from the saved state. I am guessing that a lot of work could be done by creating new Interpolators that replicate the usual Interpolators but can start somewhere in the middle of an animation.

      Reply
  3. vikas kumar jha

    Is there any alternative of Animation class fillAfter() in ObjectAnimator class.
    i.e I want to put my view in initial stage after animation using Objectanimator, what i have to do??
    Please reply me
    Thanks in advance

    Reply
    1. Mikail (Post author)

      You can create a listener and reset the Views position in the onAnimationEnd method.

      Reply
  4. gumuruh

    hey… good tutorials written on this blog. :D

    i wonder,
    how to make a springs effect -like in android by using the animation concept you’ve just explained on several articles previously?

    IF let say, we have a mass object hangs on that springs. And we would like to animate the mass object dropped down along with the springs movement. Would it be possible for making the effect real using a casual sprite / anothing thing need to be added?

    Reply

Leave a Comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>