Using “animation layers” to build complex bitmaps animations in Android

For the android game WhoSaid we wanted to animate a character reading (we call it will). But we wanted it to appear completely random.

will

We wanted the character to move the eyebrows, the eyes and the mouth separately. But the number of combinations were too high (120 frames), making almost impossible not to crash the app, and complicating the xml for the animation.

So we decided to divide the character into three “animation layers”: body + eyebrows (3 frames), eyes (5 frames) and mouth (8 frames). 16 frames in total, but this way we have 120 combinations.

Layout configuration

So we figured out who to do it minimizing the number of images. We used a FrameLayout as container of the layers. And each animation layer is an ImageView.

In the layout file we must include a FrameLayout where we are going to put the ImageViews. In this case we have three ImageViews, one for the eyebrows animation , other for the eyes animation and the last for the mouth animation.

The order of the ImageViews is important. The first ImageView is the base, and the later views are in top of that in descending order. In this case the base goes first, and later the eyes view.

Here is the layout file:

<?xml version="1.0" encoding="utf-8"?>

<!--Container of the three layers-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:padding="10dp">

    <!--First layer -> Base layer-->
    <ImageView
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/willBase"
        android:src="@drawable/will_transparent" />

    <!--Second layer-->
    <ImageView
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/willEyes"
        android:src="@drawable/will_transparent" />

    <!--Third layer -> Top layer-->
    <ImageView
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/willMouth"
        android:src="@drawable/will_transparent" />
</FrameLayout>

Don’t forget to set the src property of your ImageViews to a transparent bitmap of the same size of your frames. If not the images of the animation will stretch to occupy all the view without maintaining proportions.

Drawable configuration

First of all, all the frames must be the same size.

Frame sizes must be equals.

Frame sizes must be equals.

And now the configuration for the three layers.

First layer (base layer)

Here we have the tree images that compound our base animation layer.

Eyebrows animation.

Base image with the three frames that compose the eyebrows animation.

With this three images we create an animation drawable:

<?xml version="1.0" encoding="utf-8"?>

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">

    <item android:drawable="@drawable/base_eyebrows_no_mouth_001" android:duration="4350"/>
    <item android:drawable="@drawable/base_eyebrows_no_mouth_002" android:duration="4375"/>
    <item android:drawable="@drawable/base_eyebrows_no_mouth_003" android:duration="4250"/>

</animation-list>

Second layer, eyes animation

And in top of that we will set our eyes animation, composed by five frames.

Frames that compose the eyes animation.

Frames that compose the eyes animation.

And the xml for this animation:

<?xml version="1.0" encoding="utf-8"?>

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/will_eyes_001" android:duration="810"/>
    <item android:drawable="@drawable/will_eyes_002" android:duration="810"/>
    <item android:drawable="@drawable/will_eyes_003" android:duration="810"/>
    <item android:drawable="@drawable/will_eyes_001" android:duration="910"/>
    <item android:drawable="@drawable/will_eyes_002" android:duration="910"/>
    <item android:drawable="@drawable/will_eyes_003" android:duration="810"/>
    <item android:drawable="@drawable/will_eyes_004" android:duration="50"/>
    <item android:drawable="@drawable/will_eyes_005" android:duration="50"/>

</animation-list>

Third layer, mouth

Frames that compound the mouth animation. I show you as a gif because there are 8 frames.

Mouth animation

And here we have the animation xml:

<?xml version="1.0" encoding="utf-8"?>

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">

    <item android:drawable="@drawable/will_mouth_001" android:duration="225"/>
    <item android:drawable="@drawable/will_mouth_008" android:duration="275"/>
    <item android:drawable="@drawable/will_mouth_004" android:duration="250"/>
    <item android:drawable="@drawable/will_mouth_006" android:duration="225"/>
    <item android:drawable="@drawable/will_mouth_002" android:duration="250"/>
    <item android:drawable="@drawable/will_mouth_006" android:duration="275"/>
    <item android:drawable="@drawable/will_mouth_007" android:duration="250"/>
    <item android:drawable="@drawable/will_mouth_003" android:duration="300"/>
    <item android:drawable="@drawable/will_mouth_004" android:duration="250"/>
    <item android:drawable="@drawable/will_mouth_008" android:duration="350"/>
    <item android:drawable="@drawable/will_mouth_004" android:duration="225"/>
    <item android:drawable="@drawable/will_mouth_003" android:duration="250"/>
    <item android:drawable="@drawable/will_mouth_006" android:duration="280"/>
    <item android:drawable="@drawable/will_mouth_007" android:duration="300"/>
    <item android:drawable="@drawable/will_mouth_003" android:duration="275"/>
    <item android:drawable="@drawable/will_mouth_008" android:duration="375"/>
    <item android:drawable="@drawable/will_mouth_002" android:duration="250"/>
    <item android:drawable="@drawable/will_mouth_007" android:duration="350"/>
    <item android:drawable="@drawable/will_mouth_006" android:duration="250"/>
    <item android:drawable="@drawable/will_mouth_003" android:duration="240"/>
    <item android:drawable="@drawable/will_mouth_001" android:duration="225"/>

</animation-list>

You will notice that there are repeated drawables. That not increase the memory that the animation occupy, but it gives a more variety animation.

Code configuration

And last but not least we have to code our animation.

private void animateWill() {
    ImageView willBase = (ImageView) getView().findViewById(R.id.willBase);
    willBase.setAdjustViewBounds(true);
    willBase.setBackgroundResource(R.drawable.will_base_eyebrows_animation);
    AnimationDrawable eyebrowsAnimation =(AnimationDrawable) willBase.getBackground();
    eyebrowsAnimation.start();

    ImageView willEyes = (ImageView) getView().findViewById(R.id.willEyes);
    willEyes.setAdjustViewBounds(true);
    willEyes.setBackgroundResource(R.drawable.will_eyes_animation);
    AnimationDrawable readingAnimation =(AnimationDrawable) willEyes.getBackground();
    readingAnimation.start();

    ImageView willMouth = (ImageView) getView().findViewById(R.id.willMouth);
    willMouth.setAdjustViewBounds(true);
    willMouth.setBackgroundResource(R.drawable.will_mouth_animation);
    AnimationDrawable mouthAnimation =(AnimationDrawable) willMouth.getBackground();
    mouthAnimation.start();
}

Basically you have to find the view, set the background resource, prepare the animation and start it.

Memory problems

If you are careless it’s easy to run out of memory using this method, cause you are working with lots of bitmaps. It’s strongly recommend to use custom drawables for each pixel density. Each pixel counts especially on low-level devices.

Be careful with Roboguice and creating and destroying Activities/Fragments that has animations.  When finishing an Activity/Fragment that extends a RoboActivity/RoboFragment it’s not destroyed immediately, it has to wait for a Finalizer to be garbage collected. It will occur but It will not fast, even if you call the garbage collector. So if you create a lot of views you will face a problem.

For this game we only used Roboguice to inject views, so we decided to implement the view injection by ourselves in some kind of framework we are building for future applications (that function it’s very few code indeed). We will release it soon, when it is a little bit more mature.

Advertisements

5 thoughts on “Using “animation layers” to build complex bitmaps animations in Android

  1. “even if you call the garbage collector. ” – You don’t really call the GC you just hint to the system that now would be a good time to run GC. There is no guarantee it’s actually triggered at the time you issue the System.gc().

    • What I refer was that even if you call System.gc() and the garbage collector is triggered it will not free the memory if the Finalizer didn’t terminate the object.

  2. I have a question:
    Where in the code you are telling the small layers where to be put ?
    For example how does the eyes-layer know where to be positioned?
    also, why did you want to set the “src” to the imageViews to be transparent?

    • Sorry, I inject the views and forget to put that part. I fix it.
      In the code you have to find the ImageViews by Id and the apply the Animation. For example for the eyes layer.

      ImageView willEyes = (ImageView) getView().findViewById(R.id.willEyes);
      willEyes.setAdjustViewBounds(true);
      willEyes.setBackgroundResource(R.drawable.will_eyes_animation);
      AnimationDrawable readingAnimation =(AnimationDrawable) willEyes.getBackground();
      readingAnimation.start();

      About the src property, if you don’t set the src to transparent your view will stretch to occupy all the view, without maintaining the proportions.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s