Android Property Animation — ValueAnimator

This is the second post on Android Property Animation. Previous post can be found below.

Android Property Animation Overview.

ValueAnimator is the core of the Property Animation system. It provides a timing engine which calculates the animation values. It also allows us to get notified at every animation frame through ValueAnimator.AnimatorUpdateListener interface.

Animation Handler

All animations created by Property Animation system share a single timing pulse, which is maintained by a custom static handler AnimationHandler at ValueAnimator class. Below is the code extracted from NineOldAndroids library ValueAnimator class.

/**

 * This custom, static handler handles the timing pulse that is shared by

 * all active animations. This approach ensures that the setting of animation

 * values will happen on the UI thread and that all animations will share

 * the same times for calculating their values, which makes synchronizing

 * animations possible.

 *

 */

private static class AnimationHandler extends Handler {

    /**

     * There are only two messages that we care about: ANIMATION_START and

     * ANIMATION_FRAME. The START message is sent when an animation's start()

     * method is called. It cannot start synchronously when start() is called

     * because the call may be on the wrong thread, and it would also not be

     * synchronized with other animations because it would not start on a common

     * timing pulse. So each animation sends a START message to the handler, which

     * causes the handler to place the animation on the active animations queue and

     * start processing frames for that animation.

     * The FRAME message is the one that is sent over and over while there are any

     * active animations to process.

     */

    @Override

    public void handleMessage(Message msg) {

        boolean callAgain = true;

        ArrayList<ValueAnimator> animations = sAnimations.get();

        ArrayList<ValueAnimator> delayedAnims = sDelayedAnims.get();

        switch (msg.what) {

            // TODO: should we avoid sending frame message when starting if we

            // were already running?

            case ANIMATION_START:

                ArrayList<ValueAnimator> pendingAnimations = sPendingAnimations.get();

                if (animations.size() > 0 || delayedAnims.size() > 0) {

                    callAgain = false;

                }

                // pendingAnims holds any animations that have requested to be started

                // We're going to clear sPendingAnimations, but starting animation may

                // cause more to be added to the pending list (for example, if one animation

                // starting triggers another starting). So we loop until sPendingAnimations

                // is empty.

                while (pendingAnimations.size() > 0) {

                    ArrayList<ValueAnimator> pendingCopy =

                            (ArrayList<ValueAnimator>) pendingAnimations.clone();

                    pendingAnimations.clear();

                    int count = pendingCopy.size();

                    for (int i = 0; i < count; ++i) {

                        ValueAnimator anim = pendingCopy.get(i);

                        // If the animation has a startDelay, place it on the delayed list

                        if (anim.mStartDelay == 0) {

                            anim.startAnimation();

                        } else {

                            delayedAnims.add(anim);

                        }

                    }

                }

                // fall through to process first frame of new animations

            case ANIMATION_FRAME:

                // currentTime holds the common time for all animations processed

                // during this frame

                long currentTime = AnimationUtils.currentAnimationTimeMillis();

                ArrayList<ValueAnimator> readyAnims = sReadyAnims.get();

                ArrayList<ValueAnimator> endingAnims = sEndingAnims.get();


                // First, process animations currently sitting on the delayed queue, adding

                // them to the active animations if they are ready

                int numDelayedAnims = delayedAnims.size();

                for (int i = 0; i < numDelayedAnims; ++i) {

                    ValueAnimator anim = delayedAnims.get(i);

                    if (anim.delayedAnimationFrame(currentTime)) {

                        readyAnims.add(anim);

                    }

                }

                int numReadyAnims = readyAnims.size();

                if (numReadyAnims > 0) {

                    for (int i = 0; i < numReadyAnims; ++i) {

                        ValueAnimator anim = readyAnims.get(i);

                        anim.startAnimation();

                        anim.mRunning = true;

                        delayedAnims.remove(anim);

                    }

                    readyAnims.clear();

                }


                // Now process all active animations. The return value from animationFrame()

                // tells the handler whether it should now be ended

                int numAnims = animations.size();

                int i = 0;

                while (i < numAnims) {

                    ValueAnimator anim = animations.get(i);

                    if (anim.animationFrame(currentTime)) {

                        endingAnims.add(anim);

                    }

                    if (animations.size() == numAnims) {

                        ++i;

                    } else {

                        // An animation might be canceled or ended by client code

                        // during the animation frame. Check to see if this happened by

                        // seeing whether the current index is the same as it was before

                        // calling animationFrame(). Another approach would be to copy

                        // animations to a temporary list and process that list instead,

                        // but that entails garbage and processing overhead that would

                        // be nice to avoid.

                        --numAnims;

                        endingAnims.remove(anim);

                    }

                }

                if (endingAnims.size() > 0) {

                    for (i = 0; i < endingAnims.size(); ++i) {

                        endingAnims.get(i).endAnimation();

                    }

                    endingAnims.clear();

                }


                // If there are still active or delayed animations, call the handler again

                // after the frameDelay

                if (callAgain && (!animations.isEmpty() || !delayedAnims.isEmpty())) {

                    sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay -

                        (AnimationUtils.currentAnimationTimeMillis() - currentTime)));

                }

                break;

        }

    }

}

The handler only handles two types of messages, including ANIMATION_START and ANIMATION_FRAME.

ANIMATION_START message is generated when an animation starts, the handler will receive the message and put the animation into delay queue. Next the handler decide if the animations from the delay queue should be started immediately (add to ready queue). The handler will then process all active animations, which includes those in the ready queue.

ANIMATION_FRAME message is sent by the handler itself to schedule next animation frame update. The handler checks if any animation from the delay queue should be started and then process all active animations. (The code to handle ANIMATION_FRAME is actually part of the code for handling ANIMATION_START).

The handler also checks if an animation should be ended or not in the handler.

By using this static handler, all animations are synchronized on a frame by frame basis.

Using ValueAnimator

Animating a single value with ValueAnimator is straightforward. ValueAnimator provides static method like ofInt and ofFloat to animation between integer and floating point values. We will skip that and discuss how to animation multiple values.

Suppose we need to animate an ImageView of a red square from bottom left to top right as shown in the screenshots below.

 

Figure 1. Animation Bottom Left to Top Right

It is obvious that we need to update both the x and y coordinates. The naive approach is to create two animations and play them simultaneously, but it is not efficient. We can use PropertyValuesHolder to combine multiple values. Alternatively, we can use custom objects as animation values. We’ll explore both approaches.

We will use the layout as indicated by the xml file below. We want to move the ImageView inside the FrameLayout with id “container”.

 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context=".MainActivity" >


    <FrameLayout

        android:id="@+id/container"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:layout_alignParentTop="true">

    </FrameLayout>

    <LinearLayout

        android:id="@+id/controlBtns"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:orientation="horizontal"

        android:layout_alignParentBottom="true">

        <Button

            android:id="@+id/btnStart"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="Start">

        </Button>

        <Button

            android:id="@+id/btnReset"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="Reset">

        </Button>

    </LinearLayout>

    <ImageView

        android:id="@+id/image"

        android:layout_width="50dp"

        android:layout_height="50dp"

        android:src="#ffff4444"

        android:layout_above="@id/controlBtns">

    </ImageView>

</RelativeLayout>

Using PropertyValuesHolder

PropertyValuesHolder contains information about a property and its values during animation. It is used with ValueAnimator or ObjectAnimator to animate multiple properties at the same time.

In order to support property animation in pre-3.0 Android devices, NineOldAndroids provide a class AnimatorProxy to wrap a view and handles drawing of animated views. We need to wrap the ImageView in our code as below.

ImageView mImageView = (ImageView) this.findViewById(R.id.image);

mImageAnimatorProxy = AnimatorProxy.wrap(mImageView);

The code below creates an animation which animates two values which represent X and Y position of the image view with respective to the frame layout.

PropertyValuesHolder widthPropertyHolder = PropertyValuesHolder.ofFloat("posX", mImageAnimatorProxy.getX(), container.getWidth() - mImageView.getWidth());

PropertyValuesHolder heightPropertyHolder = PropertyValuesHolder.ofFloat("posY", mImageAnimatorProxy.getY(), 0);

ValueAnimator mTranslationAnimator = ValueAnimator.ofPropertyValuesHolder(widthPropertyHolder, heightPropertyHolder);

mTranslationAnimator.addUpdateListener(ValueAnimatorDemo.this);

mTranslationAnimator.setDuration(1000);

mTranslationAnimator.start();

We use two PropertyValuesHolder to animate two values named posX and posY. We then created the animation using ValueAnimator. We registered the listener, set the duration and start the animation. The listener interface is implemented by overriding the onAnimationUpdate callback function as below.

@Override

public void onAnimationUpdate(ValueAnimator arg0) {

    float posX = (Float) arg0.getAnimatedValue("posX");

    float posY = (Float) arg0.getAnimatedValue("posY");

    mImageAnimatorProxy.setX(posX);

    mImageAnimatorProxy.setY(posY);

}

We retrieve the animated values posX and posY at every animation frame and update it.

Using Custom Objects as Animation Values

ValueAnimator can be used to animate custom objects also. We’ll need to specify a TypeEvaluator to tell the ValueAnimator how the values should be calculated.

In our example, we are moving the position of the ImageView, so we can create a custom object to represent the position of the ImageView and animate it. The code below creates a class Position to represent the position of the ImageView.

private class Position {

    private float posX;

    private float posY;


    public float getPosX() {

        return posX;

    }


    public float getPosY() {

        return posY;

    }


    Position(float pPosX, float pPosY) {

        posX = pPosX;

        posY = pPosY;

    }

}

We also provide to TypeEvaluator by implementing the android.animation.TypeEvaluator<T> interface.

private class PositionTypeEvaluator implements TypeEvaluator<Position> {

    @Override

    public Position evaluate(float fraction, Position startValue, Position endValue) {

        float posX = startValue.getPosX() + (endValue.getPosX() - startValue.getPosX()) * fraction;

        float posY = startValue.getPosY() + (endValue.getPosY() - startValue.getPosY()) * fraction;

        return new Position(posX, posY);

    }

}

At every animation frame, the callback function evaluate will be triggered with three arguments, including the elapsed time so far, the animation start position and end position. We can calculate the current position should be based on the three input arguments and returns it.

With the custom object and TypeEvaluator in place, the animation can be easily created as below.

ValueAnimator mAnimator = ValueAnimator.ofObject(new PositionTypeEvaluator(), new Position(mOriX, mOriY),

                        new Position(container.getWidth() - mImageView.getWidth(), 0));

mAnimator.addUpdateListener(ValueAnimatorDemo2.this);

mAnimator.setDuration(1000);

mAnimator.start();

Of course, we still need to implement the ValueAnimator.AnimatorUpdateListener interface.

@Override

public void onAnimationUpdate(ValueAnimator pAnimator) {

    Position currentPos = (Position) pAnimator.getAnimatedValue();

    mImageAnimatorProxy.setX(currentPos.getPosX());

    mImageAnimatorProxy.setY(currentPos.getPosY());

}

Note that we can also use ViewPropertyAnimator, which I’ll cover in another post if I got time.

You can get the full source code here: https://github.com/roman10/roman10-android-tutorial/tree/master/PropertyAnimation

References:

Android ValueAnimator doc: http://developer.android.com/reference/android/animation/ValueAnimator.html

Android Property Animation Overview

Property animation is introduced in Android 3.0 Honeycomb. A whole new set of APIs comes with the android.animation package (The view animation is exposed through android.view.animation package), which makes animation in Android more flexible and powerful.

1. Limitation of View Animation System

The property animation system is introduced to tackle the limitations of the view animation systems in pre-3.0 Android as described below.

Firstly, we can only animate views. This is enough most of the times since animation manipulates the GUI, which consists of views. However, there are times we want to animate non-view objects or properties, then we are on our own. For example, if we have a custom view implemented by drawing a few drawables on canvas and we want to animate the individual drawable, we won’t be able to use the view animation system.

Secondly, the animation is constrained to position, size, rotation etc. Properties like background color is not supported by the view animation system.

Thirdly, the view animation system only updates where the view is drawn, but not the view itself. This can cause issues sometimes. For example, if we animate a button to move outside of current screen, after the animation ends, the button is not shown on screen, but we can still click the button’s original location to trigger click event. This is because the location of the button is not changed, though its not drawn on the screen. In this case, we’ll need to write additional code to make sure the button behave properly.

2. The android.animation package

The property animation system APIs are exposed by the android.animation package. The hierarchy of the main classes in the package is as below.

Fig 1. android.animation class hierarchy

ValueAnimator: it is the core the property animation system. There’re two steps to animate a property, including calculating the animated values and assign the values to the property of the animated object. The ValueAnimator can handle the first part but not the second.

Fig 2. ValueAnimator class

The class allows us to specify a time interpolator to control the speed and type evaluator to control the animation values. We’ll cover more about those in future posts.

ObjectAnimator: it’s a subclass of ValueAnimator, which handles both steps of the property animation for us. In other words, it updates the target property value when calculating the animated values.

AnimatorSet: it provides interfaces to group animations together. We can play animations together, sequentially, one after another, etc.

TimeAnimator: This class is introduced in API level 16 (Android 4.1 Jelly Bean). It provides a simple callback mechanism with the TimeAnimator.TimeListener interface. All animators in the system is synchronized by Android system animation frame event. TimeAnimator allows us to get notified with the animation frame event through the TimeListener interface. The method onTimeUpdate will be triggered with the TimeAnimator instance, the total animation time elapsed since the animator started and the time elapsed since preview frame, in milliseconds.

3. NineOldAndroids Library

NineOldAndroids is an amazing library which enables property animation all the way back to Android 1.0. Well, not everything of property animation, but most of it. (e.g. layout animation is left out.)

In order to support the property animation, the library use a wrapper class named AnimatorProxy to wrap a view. The wrapper class facilitates modification of post-3.0 view properties on pre-3.0 platforms and draw the view property.

References:

1. NineOldAndroids: http://nineoldandroids.com/

2. Google Developer Blog, Animation in Honeycomb: http://android-developers.blogspot.sg/2011/02/animation-in-honeycomb.html

3. Android API Guides, Property Animation: http://developer.android.com/guide/topics/graphics/prop-animation.html