Android Property Animations: The TimeAnimator

In the previous post on property animations we have come to know two types of animators. The ObjectAnimator animates a property of a single object. The AnimatorSet is a container that manages multiple Animator objects and has the functionality to choreograph complex animations. The functionality of the two classes is very general and should be sufficient in most scenarios. In some rare cases, however, the two classes are not sufficient and you need some more low-level access to the animation data. This is the use case of the TimeAnimator. The TimeAnimator was added in API 16 and provides a low level interface to the property animation system.

Imagine that you want to animate a property of an object that does not have a setter method. Then you could either modify the object and add a setter method for the animated property. But sometimes this is not possible because the object is defined by some external library. Another solution would be to write a wrapper class that holds an instance of the object to be animated and provides a setter method that allows you to use the ObjectAnimator. Another solution would be to use the TimeAnimator. The time animator is particularly useful if you don’t need to worry about interpolators but instead want to know explicitly the elapsed time in milliseconds.

The TimeAnimator doesn’t actually animate anything itself. It relies on a TimeListener to do the work and is only responsible for calling the TimeListener every time the animation is updated. TimeListener is an interface that follows the observer design pattern. TimeAnimator defines only two public methods.

void setTimeListener(TimeAnimator.TimeListener listener)
void start()

The method setTimeListener is used to register the time listener with the animation. In contrast to the listeners that we encountered in the previous tutorial on property animations, the time animator hold only a single time listener. Calling setTimeListener will remove any previously registered listener from the animator. The start method behaved in the same way as for the other Animator classes. Calling it will start the animation. Note that the TimeAnimator extends the Animator class. This means that you can still call all of the methods defined in Animator, such as setDuration and setStartDelay, or control the animation’s flow with start, end, cancel, pause and resume, or add AnimatorListener or AnimatorPauseListener objects. However, the TimeAnimator does not listen to its duration. It will keep on running until it is explicitly stopped by calling end or cancel.

If you want to make use of the TimeAnimator then you have to implement your own time listener. The TimeListener is a very simple interface defining only one method.

void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime)

When you implement a TimeListener and register it with the TimeAnimator then the TimeAnimator will call the method onTimeUpdate every time that the animation is updated. Three arguments are passed to the method. The first argument is the TimeAnimator that issues the call. The object is passed to the TimeListener because the listener could be registered with multiple animations. This way the listener knows which animation was updated. The second and third argument provide information about the animation time. totalTime is the time in milliseconds that the animation has been running. The argument deltaTime is the time in milliseconds that has elapsed since the last update of the animation.

Example: Showing Frames per Second

As always, it is best to understand the TimeAnimator by example. In this tutorial we will develop a time animator that displays the frames per second (fps) information on the screen as another animation is running. We will first create an implementation of TimeListener that updates a TextView with the fps number. The class will contain a double member that holds the fps value and a reference to the TextView that should be updated.

class FpsTimeListener implements TimeListener {
  private double fps;
  private TextView textView;

  public FpsTimeAnimator(TextView textView) {
    this.textView = textView;
    this.fps = -1.0;
  }

  void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
    // code follows below
  }
}

The constructor takes the reference to the text view object and initializes the fps number with a negative value. In the calculation of fps we will be using a running average to reduce fluctuations in the output. The negative value indicates that the value has not yet been calculated. The current fps value is calculated simply from the deltaTime value.

double currentFps = 1000.0/(double)deltaTime;

However, at the start of the animation deltaTime can be zero. In this case the calculation above would result in infinity. We therefore have to modify the code like follows.

double currentFps;
if (deltaTime != 0)
  currentFps = 1000.0 / (double) deltaTime;
else
  currentFps = 0.9 * fps;

If this is the first evaluation of the frames per second, the fps value is set directly. If, on the other hand, fps already contains a positive number than a running average calculation is performed.

if (fps<0.0)
  fps = currentFps;
else
  fps = 0.9*fps + 0.1*currentFps;

Finally, the text field has to be updated with the fps value.

textView.setText(String.format("fps: %.2f",fps));

Next we create a simple animation. We use a similar layout to the animation in the previous tutorials. We have an ImageView with id some_image and we have a TextView which we call fps_text. In our activity we first create an animation that will rotate the image 5 times by 360 degrees. We do this in the onCreate method of our activity.

protected void onCreate(Bundle savedInstanceState) {
  ImageView someImage = (ImageView) findViewById(R.id.some_image);

  ObjectAnimator rotateAnim = ObjectAnimator.ofFloat(someImage, "rotation", 0, 360);
  rotateAnim.setDuration(1000);
  rotateAnim.setRepeatCount(5);
  rotateAnim.setRepeatMode(ObjectAnimator.RESTART);
Next we create a FpsTimeListener that will update the fps_text TextView.
  fpsText = (TextView) findViewById(R.id.fps_text);
  FpsTimeListener listener = new FpsTimeListener(fpsText);

This listener is added to a TimeAnimator that we will declare final for reasons that we will see later.

  final TimeAnimator timeAnim = new TimeAnimator();
  timeAnim.setTimeListener(listener);

Now we collect the two animations in an AnimatorSet and instruct the set to play both animations together.

  anim = new AnimatorSet();
  anim.play(rotateAnim).with(timeAnim);

We are ready to start the animation. We do this in the callback method startAnimation which is linked to the image’s onClick event.

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

This will work nicely, but if you try it out you will notice that the TimeAnimator will never stop. This is because it does not listen to its duration but, as explained above, needs to be stopped manually by calling the end method. In order to achieve this we add the following code to the onCreate method.

  rotateAnim.addListener(new AnimatorListenerAdapter() {
    public void onAnimationEnd(Animator animation) {
      timeAnim.end();
    }
  });
Time Animator Example

Time Animator example showing the frames per second of an animation

We only want to receive a callback when the animation ends. We use the convenience class AnimatorListenerAdapter described in the previous tutorial and override the onAnimationEnd method.
This will stop the timeAnim animation when the rotateAnim animation has ended. Note that the call to timeAnim.end inside the anonymous class requires timeAnim to be declared final. This is the reason for declaring it final above. The resulting animation can be seen on the right. In this case we see that, for this simple animation, we obtain a rate of around 60 frames per second. This is a good value and results in a nice smooth appearance of the animation.

This tutorial concludes the series on property animation. If you found the tutorials useful, please spread the word. You can also follow me on Google+, Facebook or Twitter.

Mikail

3 Comments

  1. Venkatesan

    Looks great tutorial.I need one help,Currently I am working gridview calendar with listview.In the main Layout I have placed Gridview in top,Listview is placed under gridview.In the gridview I am displaying the current week only.When a user scroll down I am displaying the whole month and the listview is displayed under gridview.I need to animate the listview, when a user scroll down along with gridview i.e when a user scroll up the listview will move upward along with gridview with animation, when user scroll down listview will move downwards along with gridview with animation.Please help me to achieve this.

    Regards,
    Venkatesan.R

    Reply
  2. Manoj Aggarwal

    Hi,
    I am trying to measure FPS of an Animation Set where animators in the animator set is animating property “left”. The code is below:
    package com.example.fontproto;

    import java.util.ArrayList;

    import android.animation.Animator;
    import android.animation.Animator.AnimatorListener;
    import android.animation.AnimatorListenerAdapter;
    import android.animation.AnimatorSet;
    import android.animation.ObjectAnimator;
    import android.animation.TimeAnimator;
    import android.app.Activity;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.Menu;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.ImageView;
    import android.widget.TextView;

    public class MainActivity extends Activity {

    AnimatorSet set = new AnimatorSet();
    private TextView fpsText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    ImageView someImage = (ImageView) findViewById(R.id.some_image);
    someImage.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View v) {
    set.start();

    }
    });

    //ObjectAnimator rotateAnim = ObjectAnimator.ofFloat(someImage, “rotation”, 0, 360);
    ObjectAnimator leftAnim = ObjectAnimator.ofFloat(someImage, “left”, 0, 300);
    // ObjectAnimator leftAnim2 = ObjectAnimator.ofInt(someImage, “top”, 0, 100);

    ArrayList list = new ArrayList();
    // rotateAnim.setDuration(1000);
    // rotateAnim.setRepeatCount(5);
    // rotateAnim.setRepeatMode(ObjectAnimator.RESTART);
    leftAnim.setDuration(1000);
    leftAnim.setRepeatCount(5);
    leftAnim.setRepeatMode(ObjectAnimator.RESTART);
    // leftAnim2.setDuration(1000);
    // leftAnim2.setRepeatCount(5);
    // leftAnim2.setRepeatMode(ObjectAnimator.RESTART);

    fpsText = (TextView) findViewById(R.id.fps_text);
    FpsTimeListener listener = new FpsTimeListener(fpsText);
    final TimeAnimator timeAnim = new TimeAnimator();
    timeAnim.setTimeListener(listener);

    list.add(leftAnim);
    // list.add(leftAnim2);
    //list.add(rotateAnim);
    list.add(timeAnim);
    set.playTogether(list);
    // Listen for animation Completed event
    set.addListener(new AnimatorListener() {

    @Override
    public void onAnimationCancel(Animator animation) {
    int i =1;
    Log.v(“tag”, “msg”);
    }

    @Override
    public void onAnimationEnd(Animator animation) {
    Log.v(“tag”, “msg”);
    //timeAnim.end();
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
    Log.v(“tag”, “msg”);
    }

    @Override
    public void onAnimationStart(Animator animation) {
    Log.v(“tag”, “msg”);
    }

    });
    // rotateAnim.addListener(new AnimatorListenerAdapter() {
    // public void onAnimationEnd(Animator animation) {
    // timeAnim.end();
    // }
    // });
    return true;
    }

    }

    When i start the animation without timeAnimator, the animation works and i can see the image going from left to right. But when i add timeAnimator, the “left” animation stops working. However, if i replace left with rotate property, it works smoothly as explained by you above. Is there something i am missing? Does timeAnimator not work with “left” property?

    Please let me know.
    Thanks
    Manoj

    Reply
    1. Mikail (Post author)

      Hi Manoj,

      Without knowing exactly how you implemented FpsTimeListener and what your layout looks like, these sort of errors are not easily found.

      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>