Sunday, 9 April 2017

Frame Animations in Android

Animations add vivacity and personality to your apps. Let’s take a look at how to implement a subcategory of animations called “Frame Animations,” meaning that they’re drawn frame by frame.
In Google’s official Material Design spec, there’s an entire page dedicated to Delightful Details, which has wonderful examples of Frame Animations in action.



Nifty animations! Unfortunately, nothing on the page links to resources for actually creating those delightful details, so here I am to help! Specifically, we’re going to walk through making an empty heart animate into a filled-up heart, then vice versa. It’ll look something like this:

…beautiful, I know.

Sequence of Images

The idea behind a frame animation is simple: We’ll be cycling through a series of images very quickly, just like an old movie reel. The “frame” refers to a single image. Thus, the first step in creating a custom frame animation is to create a sequence of images.
We have two options here: we can use XML drawables (such as shape drawables) or actual image files. For the sake of simplicity, we’re going to use the following series of PNG images:





In a production application, we would also make sure to have images sized appropriately for different screen densities. For now, shove those images into the res/drawable-mdpi folder and call it a day. I would also recommend naming them in a self-descriptive way, such as ic_heart_0.pngic_heart_1.png and so on. This way, we know the order of the images without having to view them.
I chose to name my heart images by their respective filled-up percentage, because I’m a nerd.

XML Drawables

Now that we have our images to cycle through, the next step is to define an XML Drawable for our animation. Once again, we are faced with two possibilities: the Animation-list and the Animated-selector.

Animation-List

Animation-list is the default Frame Animation of choice, as it was introduced in API 1. It works everywhere, and it’s simple. It just cycles through a sequence of provided images in a given order with given durations.
Here’s an example of an Animation-list for my heart filling up, placed in res/drawable/animation_list_filling.xml:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
                android:oneshot="true">

    <item
        android:duration="500"
        android:drawable="@drawable/ic_heart_0"/>

    <item
        android:duration="500"
        android:drawable="@drawable/ic_heart_25"/>

    <item
        android:duration="500"
        android:drawable="@drawable/ic_heart_50"/>

    <item
        android:duration="500"
        android:drawable="@drawable/ic_heart_75"/>

    <item
        android:duration="500"
        android:drawable="@drawable/ic_heart_100"/>

</animation-list>
Each item in the list is just pointing to one of the images in our sequence from earlier. All we have to do is place them in the correct order and then add an appropriate duration in milliseconds.
And here’s an example of an Animation-list for my heart emptying, placed in res/drawable/animation_list_emptying.xml:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
                android:oneshot="true">

    <item
        android:duration="500"
        android:drawable="@drawable/ic_heart_100"/>

    <item
        android:duration="500"
        android:drawable="@drawable/ic_heart_75"/>

    <item
        android:duration="500"
        android:drawable="@drawable/ic_heart_50"/>

    <item
        android:duration="500"
        android:drawable="@drawable/ic_heart_25"/>

    <item
        android:duration="500"
        android:drawable="@drawable/ic_heart_0"/>

</animation-list>
You might notice the android:oneshot=”true” in both of these code snippets, which is simply an attribute of the animation-list for playing the animation once and then stopping. If this is set to “false,” the animation will play on repeat.
In production, the 500ms duration is a long time, but I want to exaggerate the animations for demonstration purposes. Also note that five frames isn’t very many images for a smooth transition. The number of frames to use and how long to display them is a problem to solve on an individual-implementation basis. For a frame of reference, 15 frames at 15ms is very smooth.

Animated-Selector

Animated-selector is a bit more complex, as it’s state-based. Depending on the state of a View (such as selected or activated), the selector will animate to the correct state using provided Transitions. The Animated-selector is implemented only for Lollipop (and above), so we’re going to define our XML in the -v21 package.
Here is an example of the Animated-selector, placed in res/drawable-v21/selector.xml:
<?xml version="1.0" encoding="utf-8"?>
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/on"
        android:state_activated="true">
        <bitmap
            android:src="@drawable/ic_heart_100"/>
    </item>

    <item
        android:id="@+id/off">
        <bitmap
            android:src="@drawable/ic_heart_0"/>
    </item>

    <transition
        android:fromId="@+id/on"
        android:toId="@+id/off"
        android:drawable="@drawable/animation_list_emptying">
    </transition>

    <transition
        android:fromId="@id/off"
        android:toId="@id/on"
        android:drawable="@drawable/animation_list_filling">
    </transition>

</animated-selector>
Take note of how it’s actually referencing our Animation-lists from earlier as Transitions.
This animated-selector works well, but we need to account for the non-Lollipop devices. We’re going to define a non-animated selector, placed in res/drawable/selector.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:state_activated="true">
        <bitmap android:src="@drawable/ic_heart_100"/>
    </item>

    <item
        android:state_activated="false">
        <bitmap android:src="@drawable/ic_heart_0"/>
    </item>

</selector>
Now our selector will work on any device. If tried on a pre-Lollipop device, the animated-selector will just just skip the Transitions and go directly to the end state, since we’re just using a normal selector. And of course, a Lollipop device will have our Transition that we defined in the animated-selector.
In the above snippet, the animated-selector cares about the android:state_activatedattribute. Just like a normal selector, I have different items defined for the possible states. However, I also have transitions defined for how to animate between these different states. In this particular animation, I just pointed the transitions to the animation-list drawables that we defined earlier.
We now have four XML files: one for emptying the heart, one for filling the heart, and two selectors for transitioning between empty and full states.

Set Up the ImageViews

It’s time to set up some ImageViews for us to play with. Specifically, we’re going to have three ImageViews, one for each XML Drawable that we defined previously. Put the following code in a Layout of your choice and throw it in an Activity:
<ImageView
    android:id="@+id/imageview_animation_list_filling"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/animation_list_filling"
    />

<ImageView
    android:id="@+id/imageview_animation_list_emptying"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/animation_list_emptying"
    />

 <ImageView
    android:id="@+id/imageview_animated_selector"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/selector"
    />
This is just a few ImageViews with unique ids and backgrounds pointing to our XML Drawables from earlier.

Starting the Animations

The behavior for starting the animations differs between the two implementations, so we’ll start with the animation-list.

Animation-List

In our Activity, we grab a reference to the ImageView and then start the animation, like so:
ImageView mImageViewFilling = (ImageView) findViewById(R.id.imageview_animation_list_filling);
((AnimationDrawable) mImageViewFilling.getBackground()).start();
Here’s what that looks like :
Now for its partner code (identical except for the id):
ImageView mImageViewEmptying = (ImageView) findViewById(R.id.imageview_animation_list_emptying);
((AnimationDrawable) mImageViewEmptying.getBackground()).start();
And here’s what that part looks like:

Those code snippets can be put in onCreate (automatically begins when the Activity begins) or in an OnClickListener (waits for user interaction). The choice is yours!

Animated-Selector

When using the Animated-selector, the animation will trigger whenever the state-based condition is met for the selector. In our simple sample, we’re going to add a click listener to our ImageView in the onCreate method of our Activity:
mImageViewSelector.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mImageViewSelector.setActivated(!mImageViewSelector.isActivated());
    }
});
When the user clicks on our heart, the heart will fill or empty, depending on the current state. Here’s a nice GIF of my heart looping back and forth forever (presumably with a user clicking at each full and empty state):
Heart Looping
Be kind to our hearts, users!

Delightful Details

Frame Animations have the power to surprise and delight users, plus it’s fun to add little personal touches to an app. Go forth and animate!

Labels:

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home