The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.


Animators

Users like things that move. Or fade, spin, or otherwise offer a dynamic experience.

Much of the time, such animations are handled for us by the framework. We do not have to worry about sliding rows in a ListView when the user scrolls, or as the user pans around a ViewPager, and so forth.

However, sometimes, we will need to add our own animations, where we want effects that either are not provided by the framework innately or are simply different (e.g., want something to slide off the bottom of the screen, rather than off the left edge).

Android had an animation framework back in the beginning, one that is still available for you today. However, Android 3.0 introduced a new animator framework that is going to be Android’s primary focus for animated effects going forward. Many, but not all, of the animator framework capabilities are available to us as developers via a backport.

Prerequisites

Understanding this chapter requires that you have read the core chapters of this book. Also, you should read the chapter on custom views, to be able to make sense of one of the samples.

ViewPropertyAnimator

Let’s say that you want to fade out a widget, instead of simply setting its visibility to INVISIBLE or GONE.

For a widget whose name is v, on API Level 11 or higher, that is as simple as:

v.animate().alpha(0);

Here, “alpha” refers to the “alpha channel”. An alpha of 1 is normal opacity, while an alpha of 0 is completely transparent, with values in between representing various levels of translucence.

That may seem rather simple. The good news is, it really is that easy. Of course, there is a lot more you can do here, and you might have to worry about supporting older Android versions, and we need to think about things other than fading widgets in and out, and so forth.

First, though, let’s consider what is really going on when we call animate() on a widget on API Level 11+.

Native Implementation

The call to animate() returns an instance of ViewPropertyAnimator. This object allows us to build up a description of an animation to be performed, such as calling alpha() to change the alpha channel value. ViewPropertyAnimator uses a so-called fluent interface, much like the various builder classes (e.g., Notification.Builder) — calling a method on a ViewPropertyAnimator() usually returns the ViewPropertyAnimator itself. This allows you to build up an animation via a chained series of method calls, starting with that call to animate() on the widget.

You will note that we do not end the chain of method calls with something like a start() method. ViewPropertyAnimator will automatically arrange to start the animation once we return control of the main application thread back to the framework. Hence, we do not have to explicitly start the animation.

You will also notice that we did not indicate any particulars about how the animation should be accomplished, beyond stating the ending alpha channel value of 0. ViewPropertyAnimator will use some standard defaults for the animation, such as a default duration, to determine how quickly Android changes the alpha value from its starting point to 0. Most of those particulars can be overridden from their defaults via additional methods called on our ViewPropertyAnimator, such as setDuration() to provide a duration in milliseconds.

There are four standard animations that ViewPropertyAnimator can perform:

  1. Changes in alpha channel values, for fading widgets in and out
  2. Changes in widget position, by altering the X and Y values of the upper-left corner of the widget, from wherever on the screen it used to be to some new value
  3. Changes in the widget’s rotation, around any of the three axes
  4. Changes in the widget’s size, where Android can scale the widget by some percentage to expand or shrink it

We will see an example of changing a widget’s position, using the translationXBy() method, later in this chapter.

You are welcome to use more than one animation effect simultaneously, such as using both alpha() and translationXBy() to slide a widget horizontally and have it fade in or out.

There are other aspects of the animation that you can control. By default, the animation happens linearly — if we are sliding 500 pixels in 500ms, the widget will move evenly at 1 pixel/ms. However, you can specify a different “interpolator” to override that default linear behavior (e.g., start slow and accelerate as the animation proceeds). You can attach a listener object to find out about when the animation starts and ends. And, you can specify withLayer() to indicate that Android should try to more aggressively use hardware acceleration for an animation, a concept that we will get into in greater detail later in this chapter.

To see this in action, take a look at the Animation/AnimatorFade sample app.

The app consists of a single activity (MainActivity). It uses a layout that is dominated by a single TextView widget, whose ID is fadee:

<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">

  <TextView
    android:id="@+id/fadee"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:text="@string/fading_out"
    android:textAppearance="?android:attr/textAppearanceLarge"
    tools:context=".MainActivity"/>

</RelativeLayout>

In onCreate(), we load up the layout and get our hands on the fadee widget:

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

    fadee=(TextView)findViewById(R.id.fadee);
  }

MainActivity itself implements Runnable, and our run() method will perform some animated effects:

  @Override
  public void run() {
    if (fadingOut) {
      fadee.animate().alpha(0).setDuration(PERIOD);
      fadee.setText(R.string.fading_out);
    }
    else {
      fadee.animate().alpha(1).setDuration(PERIOD);
      fadee.setText(R.string.coming_back);
    }
    
    fadingOut=!fadingOut;
    
    fadee.postDelayed(this, PERIOD);
  }

Specifically, we use ViewPropertyAnimator to fade out the TextView over a certain period (fadee.animate().alpha(0).setDuration(PERIOD);) and set its caption to a value indicating that we are fading out. If we are to be fading back in, we perform the opposite animation and set the caption to a different value. We then flip the fadingOut boolean for the next pass and use postDelayed() to reschedule ourselves to run after the period has elapsed.

To complete the process, we run() our code initially in onStop() and cancel the postDelayed() loop in onStart():

  @Override
  public void onStart() {
    super.onStart();

    run();
  }

  @Override
  public void onStop() {
    fadee.removeCallbacks(this);

    super.onStop();
  }

The result is that the TextView smoothly fades out and in, alternating captions as it goes.

However, it would be really unpleasant if all this animator goodness worked only on API Level 11+. Fortunately for us, somebody wrote a backport.

Backport Via NineOldAndroids

Jake Wharton wrote NineOldAndroids. This is, in effect, a backport of ViewPropertyAnimator and its underpinnings. There are some slight changes in how you use it, because NineOldAndroids is simply a library. It cannot add methods to existing classes (like adding animate() to View), nor can it add capabilities that the underlying firmware simply lacks. But, it may cover many of your animator needs, even if the name is somewhat inexplicable, and it works going all the way back to API Level 1, ensuring that it will cover any Android release that you care about.

NineOldAndroids is an Android library project. Android Studio users can add a implementation statement to their dependencies closure in build.gradle to pull in com.nineoldandroids:library:... (for some version indicated by ...).

Since NineOldAndroids cannot add animate() to View, the recommended approach is to use a somewhat obscure feature of Java: imported static methods. An import static statement, referencing a particular static method of a class, makes that method available as if it were a static method on the class that you are writing, or as some sort of global function. NineOldAndroids has an animate() method that you can import this way, so instead of v.animate(), you use animate(v) to accomplish the same end. Everything else is the same, except perhaps some imports, to reference NineOldAndroids instead of the native classes.

You can see this in the Animation/AnimatorFadeBC sample app.

In addition to having the NineOldAndroids JAR in libs/, the only difference between this edition and the previous sample is in how the animation is set up. Instead of lines like:

fadee.animate().alpha(0).setDuration(PERIOD);

we have:

animate(fadee).alpha(0).setDuration(PERIOD);

This takes advantage of our static import:

import static com.nineoldandroids.view.ViewPropertyAnimator.animate;

If the static import makes you queasy, you are welcome to simply import the com.nineoldandroids.view.ViewPropertyAnimator class, rather than the static method, and call the animate() method on ViewPropertyAnimator:

ViewPropertyAnimator.animate(fadee).alpha(0).setDuration(PERIOD);

The Foundation: Value and Object Animators

The preview of this section was the victim of a MITM ('Martian in the middle') attack.

Animating Custom Types

The preview of this section is in the process of being translated from its native Klingon.

Hardware Acceleration

The preview of this section is in an invisible, microscopic font.

The Three-Fragment Problem

The preview of this section was the victim of a MITM ('Martian in the middle') attack.