Drawable mutations

Android’s drawables are extremely useful to easily build applications. A Drawable is pluggable drawing container that is usually associated with a View. For instance, a BitmapDrawable is used to display images, a ShapeDrawable to draw shapes and gradients, etc. You can even combine them to create complex renderings.

Drawables allows to easily customize the rendering of the widgets without subclassing them. As a matter of fact, they are so convenient that most of the default Android apps and widgets are built using drawables; there are about 700 drawables used in the core Android framework. Because drawables are used so extensively throughout the system, Android optimizes them when they are loaded from resources. For instance, every time you create a Button, a new drawable is loaded from the framework resources (android.R.drawable.btn_default). This means all buttons across all the apps use a different drawable instance as their background. However, all these drawables share a common state, called the “constant state.” The content of this state varies according to the type of drawable you are using, but it usually contains all the properties that can be defined by a resource. In the case of a button, the constant state contains a bitmap image. This way, all buttons across all applications share the same bitmap, which saves a lot of memory.

The following diagram shows what entities are created when you assign the same image resource as the background of two different views. As you can see, two drawables are created but they both share the same constant state, hence the same bitmap:

This state sharing feature is great to avoid wasting memory but it can cause problems when you try to modify the properties of a drawable. Imagine an application with a list of books. Each book has a star next to its name, totally opaque when the user marks the book as a favorite, and translucent when the book is not a favorite. To achieve this effect, you would probably write the following code in your list adapter’s getView() method:

Book book = ...;
TextView listItem = ...;

listItem.setText(book.getTitle());

Drawable star = context.getResources().getDrawable(R.drawable.star);
if (book.isFavorite()) {
  star.setAlpha(255); // opaque
} else {
  star.setAlpha(70); // translucent
}

Unfortunately, this piece of code yields a rather strange result, all the drawables have the same opacity:

This result is explained by the constant state. Even though we are getting a new drawable instance for each list item, the constant state remains the same and, in the case of BitmapDrawable, the opacity is part of the constant state. Thus, changing the opacity of one drawable instance changes the opacity of all the other instances. Even worse, working around this issue was not easy with Android 1.0 and 1.1.

Android 1.5 offers a very way to solve this issue with a the new mutate() method. When you invoke this method on a drawable, the constant state of the drawable is duplicated to allow you to change any property without affecting other drawables. Note that bitmaps are still shared, even after mutating a drawable. The diagram below shows what happens when you invoke mutate() on a drawable:

Let’s update our previous piece of code to make use of mutate():

Drawable star = context.getResources().getDrawable(R.drawable.star);
if (book.isFavorite()) {
  star.mutate().setAlpha(255); // opaque
} else {
  star. mutate().setAlpha(70); // translucent
}

For convenience, mutate() returns the drawable itself, which allows to chain method calls. It does not however create a new drawable instance. With this new piece of code, our application now behaves correctly:

If you want to learn more cool techniques, come join us at Google I/O. Members of the Android team will be there to give a series of in-depth technical sessions and answer all your questions.

6 Responses to “Drawable mutations”

  1. Thanks for this tip. I have never encountered this “problem” but I’m sure I would have thought of a bug.

    I still have a question about this mutate method: what happens if the method is called twice on the same drawable? Will mutate() create a copy of the constant state or will it do nothing (which means mutate() is aware of the number of drawable sharing the constant state and can prevent creating new constant states if the latter is used by only one drawable).

  2. Romain Guy says:

    Attempting to mutate a mutated drawable results in nothing happening. There’s actually no need to track the number of drawables sharing the constant state: when a drawable mutates, it simply throws away the shared constant state, replaces it with a copy and marks itself as mutated.

  3. Ares says:

    I have never heard of this before, and needless to say, it sparked interest. Thanks for sharing this, and I will look for more posts on this subject. Your team is way ahead of the curve!

  4. Hamy says:

    Very interesting, thanks!

    I do have a question, although I think Cyril was asking along the same path.

    In your code example (the one including mutate), how will that not create as many copies of the constant state as their are stars? Would it not be better to call mutate one one star, and then use that single, mutated item for all of the translucent stars?

    From what you said above, I don’t think this is correct. I think you are checking if the new state that would be generated is equal to any current states, and if it is equal than that state is being shared, but I am not sure.

    Thanks,
    Hamy

  5. flea papa says:

    omg! this is what i am needing!!!

    thx so much!!!!

  6. Andy Raffle says:

    It’s a very useful tip, thankyou! Although for widgets there is one small snag… mutate() won’t work through the RemoteViews interface, and there is no remoteViews.setImageViewDrawable() method, so it’s not possible (in this way) to have customisation of drawable colour/alpha settings in widgets. Rendering the widget to a local bitmap then copying that across RemoteViews is the only way to make it work.