Advanced Animator Use

The previous two articles on Android animations covered the basics of the Animation, AnimationDrawable and Animator classes. These classes provide a great starting point for introducing animations into your applications. As mentioned before, the preferred way of handling animations is to use Animator classes whenever possible. Let’s take a closer look at this generic class and more advanced Animator use.

 

Animating Arbitrary Properties

During our exploration of the Animator class, we examined the ValueAnimator as well as the ObjectAnimator. The ObjectAnimator always has a target object and manipulates an attribute of the target with each step of the animation. What if you wish to animate a property which is not a simple int or float value? This is where the power of the android.animation really gets to show off.

In our previous example we used an ObjectAnimator to animate the alpha value of a View object, which is simply a float value. What if instead we wanted to “wash out” the background color of a View during our animation? We can easily set the background color using the View.setBackgroundColor() method and it is just an int, so how is the different? If we were to animate the color as just an integer, it would not give us the desired result and instead just cycle each of the colors on the way to our final color. Because the range is so large (looking at it as just an integer) the animation happens extremely fast.
 

public class ColorWashAnimatorActivity extends Activity implements
                                       ValueAnimator.AnimatorUpdateListener {
    ...
    @Override
    protected void onCreate(Bundle savedInstance) {
        ...
        mBg = new ColorDrawable(BG_COLOR_START);
        mColorView.setBackground(mBg);
        mColorAnim1 =
            ObjectAnimator.ofInt(mBg,
                                 "color",
                                 BG_COLOR_START,
                                 BG_COLOR_END);
        mColorAnim1.setDuration(ANIM_DURATION);

        mAnimButton1.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                mColorAnim1.start();
            }
        });
    }
}

 


 
What we really want to do is lighten all of the color components to get our “wash out” type effect. Behind the scenes, the ObjectAnimator class leverages a PropertyValuesHolder class to maintain which property for an object is being animated and the values being animated between. In addition to tracking the name of the property and the value range, it also uses a TypeEvaluator to determine the updated value for the current step within the animation. The TypeEvaluator interface defines a single method which is used to determine the current value of the property based on the fraction of time from 0.0 to 1.0:

public interface TypeEvaluator<T> {
    public abstract T evaluate(float fraction, T startView, T endValue);
}

In our first example, the ofInt() method of ObjectAnimator creates a simple integer version of this interface; but, you can quickly see this interface is defined with Java generics. So a more advanced Animator use would be to animate properties which are not straightforward linear interpolations of int or float (or even custom types!) In order to get our desired effect, we need to create a PropertyValuesHolder with an alternate TypeEvaluator: the ArgbEvaluator. This class takes start and end ARGB values and linearly interpolates each of the color channels represented by the integer, which is exactly what we want! Creating the animator is a little more involved, but still not terrible.

    @Override
    protected void onCreate(Bundle savedInstance) {
        ...
        //  Simple animation of the background color to make it "wash out"
        //  by using a custom TypeEvaluator
        mWashView = findViewById(R.id.view_wash);
        mWashBg = new ColorDrawable(BG_COLOR_START);
        mWashView.setBackground(mWashBg);
        PropertyValuesHolder colorHolder =
            PropertyValuesHolder.ofObject("color",
                                          new ArgbEvaluator(),
                                          Integer.valueOf(BG_COLOR_START),
                                          Integer.valueOf(BG_COLOR_END));
        mColorAnim2 =
            ObjectAnimator.ofPropertyValuesHolder(mWashBg, colorHolder);
        mColorAnim2.setDuration(ANIM_DURATION);

        mAnimButton2 = (Button)findViewById(R.id.btn_wash);
        mAnimButton2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mColorAnim2.start();
            }
        });
    }

 


 
This simple example demonstrates how the TypeEvaluator can be leveraged to animate any type of property you can dream up for your classes. In fact, the framework APIs continue to evolve (as usual) and in API 21 (Lollipop) the android.animation package picks up a couple of new TypeEvaluator implementations:

 

Using Keyframes

The other advanced Animator feature we can leverage is the Keyframe. This abstract class allows us to modify the way our animation looks without chaining together mulitiple Animators for the same property. Building upon the previous example, let’s make our wash out color change behavior so that it starts to wash out, goes partially back to the original color then finishes the wash out. Using multiple Keyframe instances for the same property we can chain them together and even set custom interpolators for each one!

    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        //  More advanced animation of the background color wash out
        //  using Keyframes and different TimeInterpolators
        mMultiWashBg = new ColorDrawable(BG_COLOR_START);
        mMultiWashView.setBackground(mMultiWashBg);
        Keyframe startFrame = Keyframe.ofInt(0.0f, BG_COLOR_START);
        startFrame.setInterpolator(new AccelerateInterpolator());
        Keyframe reverseFrame = Keyframe.ofInt(0.3f, BG_COLOR_REVERSE_POINT);
        reverseFrame.setInterpolator(new DecelerateInterpolator());
        Keyframe resumeFrame = Keyframe.ofInt(0.6f, BG_COLOR_RESUME_POINT);
        resumeFrame.setInterpolator(new DecelerateInterpolator());
        Keyframe endFrame = Keyframe.ofInt(1.0f, BG_COLOR_END);
        endFrame.setInterpolator(new OvershootInterpolator());
        PropertyValuesHolder multiHolder =
            PropertyValuesHolder.ofKeyframe("color", startFrame, reverseFrame, endFrame);
        multiHolder.setEvaluator(new ArgbEvaluator());
        mColorAnim3 =
            ObjectAnimator.ofPropertyValuesHolder(mMultiWashBg, multiHolder);
        mColorAnim3.setDuration(ANIM_DURATION);

        mAnimButton3 = (Button)findViewById(R.id.btn_multi_keys);
        mAnimButton3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mColorAnim3.start();
            }
        });
    }
    ...

 


 
Note that in this example we set the custom TypeEvaluator after we have used the factory method to create our PropertyValuesHolder with the Keyframes. This works well for our purposes since we’re still just animating an integer, albeit in a special way. That being said, you should be careful using Keyframe objects for custom property types as there is a smaller overhead compared to using int or float based properties.

 

Animating Layout Transitions

The last Animator based animation I’ll go through is the LayoutTransition. Have you ever noticed some apps have beautiful transitions when items are being added or removed from layouts? Back in API 11 the framework picked up support for these animations during layout. What’s even better is that we can customize it for our own look and feel. It builds upon our usage of ObjectAnimator based animations, allowing us to animate just about any property of a View within a layout.

The way the layouts handle animations are based upon different events which cause layout changes: adding a child view, removing a child view or changing the layout properties of a child view. Each of these events causes animations to run in a default order which usually makes sense. The animations used are identified by a type value (integer flag):

  • When a new child is added, the other children are animated first to make room for the new child (CHANGE_APPEARING) followed by the new child being animated to appear (APPEARING)
  • When a child is removed, it animates out first (DISAPPEARING) followed by other children being animated to fill the gap which is left (CHANGE_DISAPPEARING)
  • When a child’s layout properties are changed, the other children are animated to their new properties (CHANGING).

Enabling this capability with the default animations is a snap! All that is required is to set the android:animateLayoutChanges attribute in an XML layout or by calling ViewGroup.setLayoutTransition() on a layout object. When using the attribute within XML, the default transition animations from the framework are used. These animations provide a nice look/feel and are reasonable defaults. If you want to add your own zing to your layouts, you can certainly customize these. Where it gets complicated is that for each of these major events the animations occuring apply to (potentially) multiple objects. Each animation you specify is actually cloned and applied independently to other child views with their new starting/ending layout details. For example, if you wish to have a different “appearing” animation, you would first create LayoutTransition object then replace the APPEARING animator with one of your own, as shown below.

    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        //  Load the image we are going to make "appear" or "disappear"
        mImg = findViewById(R.id.lt_img);

        //  Load the custom "appear" animation and create a LayoutTransition
        mAppearAnim = AnimatorInflater.loadAnimator(this, R.animator.custom_appear);
        mLayout = (LinearLayout)findViewById(R.id.container);
        mLT = new LayoutTransition();
        mLT.setAnimator(LayoutTransition.APPEARING, mAppearAnim);

        //  Set the start delay of our custom animation to be the
        //  duration of the "change appearing" animation so it does
        //  not begin before the other views have "made room".
        Animator ca = mLT.getAnimator(LayoutTransition.CHANGE_APPEARING);
        mAppearAnim.setStartDelay(ca.getDuration());
        mLayout.setLayoutTransition(mLT);

        //  Use a button to trigger the image to come or go
        mAppearBtn = (Button)findViewById(R.id.btn_lt);
        mAppearBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mImg.getVisibility() == View.GONE) {
                    mImg.setAlpha(0f);
                    mImg.setVisibility(View.VISIBLE);
                } else {
                    mImg.setVisibility(View.GONE);
                }
            }
        });
    }

 


 
Note that if you have very deep hierarchy layouts, this may not be the best option for you as the layout calculations of start/end values may not be correct at the time the animation is started. Similarly, this generally doesn’t work very well if you are animating layout changes while also scrolling. With a little crafting with your durations, start times and selective enable/disable of animations for certain events you can mitigate these odd effects. To remove one of the default animators, just set the specific type to null like this:

    ...
    mLT.setAnimator(LayoutTransition.CHANGE_APPEARING, null);
    ...

 

Which is Best?

This article wraps up our series on animations in Android. The different techniques we have available for animating things in Android makes it tough to choose which is best. Like many things in software and UX work, it’s ultimately up to your needs. Animators are the more preferred and efficient way. On the flip side, Animation is the only option available for things like Fragment or Activity transitions. And finally, sometimes there is no substitute for image sequences. For each of these cases, the built-in Android framework comes with some canned options but it is missing very advanced preset animations. Like many of the APIs in Android, the existing APIs provide great building blocks for you to use your imagination and build gorgeous animations for your app and your brand. Good luck making your app shine and happy experimenting!

The sample sources used in this article may be found at this GitHub repository:

https://github.com/hiq-larryschiefer/AdvAnimator.git