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:



<!--Container of the three layers-->


    <!--First layer -&gt; Base layer-->
    

    <!--Second layer-->
    

    <!--Third layer -&gt; Top layer-->
    

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:





    
    
    


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:




    
    
    
    
    
    
    
    


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:





    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    


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.

Leave a comment