ValueAnimator

As part of the property animator system, we have the ValueAnimator class. We can use it to simply animate int, float, or color variables or properties. It's quite easy to use, for instance we can animate a float value from 0 to 360 during 1500 milliseconds using the following code:

ValueAnimator angleAnimator = ValueAnimator.ofFloat(0, 360.f); 
angleAnimator.setDuration(1500); 
angleAnimator.start(); 

This is alright, but if we want to get updates of the animation and react to them, we've got to set an AnimatorUpdateListener():

final ValueAnimator angleAnimator = ValueAnimator.ofFloat(0, 360.f); 
angleAnimator.setDuration(1500); 
angleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
    @Override 
    public void onAnimationUpdate(ValueAnimator animation) { 
        angle = (float) angleAnimator.getAnimatedValue(); 
        invalidate(); 
    } 
}); 
angleAnimator.start(); 

Also, in this example, we can see we're calling invalidate()from the AnimatorUpdateListener(), so we're also telling the UI to redraw the view.

There are many things we can configure of the way the animation behaves: from the animation repeat mode, number of repetitions, and type of interpolator. Let's see it in action using the same example we used at the beginning of this chapter. Let's draw four rectangles on the screen, and rotate them using different settings of a ValueAnimator:

//top left 
final ValueAnimator angleAnimatorTL = ValueAnimator.ofFloat(0, 360.f); 
angleAnimatorTL.setRepeatMode(ValueAnimator.REVERSE); 
angleAnimatorTL.setRepeatCount(ValueAnimator.INFINITE); 
angleAnimatorTL.setDuration(1500); 
angleAnimatorTL.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
    @Override 
    public void onAnimationUpdate(ValueAnimator animation) { 
        angle[0] = (float) angleAnimatorTL.getAnimatedValue(); 
        invalidate(); 
    } 
}); 
 
//top right 
final ValueAnimator angleAnimatorTR = ValueAnimator.ofFloat(0, 360.f); 
angleAnimatorTR.setInterpolator(new DecelerateInterpolator()); 
angleAnimatorTR.setRepeatMode(ValueAnimator.RESTART); 
angleAnimatorTR.setRepeatCount(ValueAnimator.INFINITE); 
angleAnimatorTR.setDuration(1500); 
angleAnimatorTR.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
    @Override 
    public void onAnimationUpdate(ValueAnimator animation) { 
        angle[1] = (float) angleAnimatorTR.getAnimatedValue(); 
        invalidate(); 
    } 
}); 
 
//bottom left 
final ValueAnimator angleAnimatorBL = ValueAnimator.ofFloat(0, 360.f); 
angleAnimatorBL.setInterpolator(new AccelerateDecelerateInterpolator()); 
angleAnimatorBL.setRepeatMode(ValueAnimator.RESTART); 
angleAnimatorBL.setRepeatCount(ValueAnimator.INFINITE); 
angleAnimatorBL.setDuration(1500); 
angleAnimatorBL.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
    @Override 
    public void onAnimationUpdate(ValueAnimator animation) { 
        angle[2] = (float) angleAnimatorBL.getAnimatedValue(); 
        invalidate(); 
    } 
}); 
 
//bottom right 
final ValueAnimator angleAnimatorBR = ValueAnimator.ofFloat(0, 360.f); 
angleAnimatorBR.setInterpolator(new OvershootInterpolator()); 
angleAnimatorBR.setRepeatMode(ValueAnimator.REVERSE); 
angleAnimatorBR.setRepeatCount(ValueAnimator.INFINITE); 
angleAnimatorBR.setDuration(1500); 
angleAnimatorBR.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
    @Override 
    public void onAnimationUpdate(ValueAnimator animation) { 
        angle[3] = (float) angleAnimatorBR.getAnimatedValue(); 
        invalidate(); 
    } 
}); 
 
angleAnimatorTL.start(); 
angleAnimatorTR.start(); 
angleAnimatorBL.start(); 
angleAnimatorBR.start(); 

Instead of setting the initial time and calculating the time difference, we're now configuring four different ValueAnimators and triggering the invalidate calls from their onAnimationUpdate() callbacks. On these ValueAnimator, we've used different interpolators and different repeat modes: ValueAnimator.RESTART and ValueAnimator.REVERSE. On all of them we've set the repeat count to ValueAnimator.INFINITE so we can observe and compare the details of the interpolator without pressure.

On the onDraw() method we've removed the postInvalidate call, as view will be invalidated by the animations, but leaving the drawText() it's very interesting, as we'll be able to see how the OvershootInterpolator() behaves and goes beyond their maximum value.

If we run this example, we'll see the four rectangles animating with different interpolation mechanisms. Play with the different interpolators, or even implement your own interpolator by extending TimeInterpolator and implement the getInterpolation(float input) method.

The input parameter of the getInterpolation method will be between 0 and 1, mapping 0 to the beginning of the animation and 1 to its end. The return value should be between 0 and 1, but could be lower or/and higher if we want to go beyond the original values like, for example, the OvershootInterpolator. The ValueAnimator will then compute the right value between the initial and final values based on this factor.

This example needs to be seen on an emulator or real device, but adding a bit of motion blur to the screenshot slightly shows the rectangles are animating at different speeds and accelerations.