blob: c5818fcbafb9d7b2db1e7c2376993602bf2de6d1 [file] [log] [blame]
Dirk Dougherty22558d02009-12-10 16:25:06 -08001page.title=Drawable Mutations
Scott Main796ce772011-02-16 10:04:45 -08002parent.title=Articles
3parent.link=../browser.html?tag=article
Dirk Dougherty22558d02009-12-10 16:25:06 -08004@jd:body
5
6<p>Android's drawables are extremely useful to easily build applications. A
7{@link android.graphics.drawable.Drawable Drawable} is a pluggable drawing
8container that is usually associated with a View. For instance, a
9{@link android.graphics.drawable.BitmapDrawable BitmapDrawable} is used to display
10images, a {@link android.graphics.drawable.ShapeDrawable ShapeDrawable} to draw
11shapes and gradients, and so on. You can even combine them to create complex
12renderings.</p>
13
14<p>Drawables allow you to easily customize the rendering of the widgets without
15subclassing them. As a matter of fact, they are so convenient that most of the
16default Android apps and widgets are built using drawables; there are about 700
17drawables used in the core Android framework. Because drawables are used so
18extensively throughout the system, Android optimizes them when they are loaded
19from resources. For instance, every time you create a
20{@link android.widget.Button Button}, a new drawable is loaded from the framework
21resources (<code>android.R.drawable.btn_default</code>). This means all buttons
22across all the apps use a different drawable instance as their background.
23However, all these drawables share a common state, called the "constant state."
24The content of this state varies according to the type of drawable you are
25using, but it usually contains all the properties that can be defined by a
26resource. In the case of a button, the constant state contains a bitmap image.
27This way, all buttons across all applications share the same bitmap, which saves
28a lot of memory.</p>
29
30<p>The following diagram shows what entities are
31created when you assign the same image resource as the background of
32two different views. As you can see, two drawables are created but they
33both share the same constant state, hence the same bitmap:</p>
34
35<img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 307px; height: 400px;" src="images/shared_states.png" alt="" id="BLOGGER_PHOTO_ID_5331437883277472082" border="0">
36
37<p>This state sharing feature is great to avoid wasting memory but it can cause
38problems when you try to modify the properties of a drawable. Imagine an
39application with a list of books. Each book has a star next to its name, totally
40opaque when the user marks the book as a favorite, and translucent when the book
41is not a favorite. To achieve this effect, you would probably write the
42following code in your list adapter's <code>getView()</code> method:</p>
43
44<pre>Book book = ...;
45TextView listItem = ...;
46
47listItem.setText(book.getTitle());
48
49Drawable star = context.getResources().getDrawable(R.drawable.star);
50if (book.isFavorite()) {
51 star.setAlpha(255); // opaque
52} else {
53 star.setAlpha(70); // translucent
54}</pre>
55
56<p>Unfortunately, this piece of code yields a rather strange result:
57all of the drawables have the same opacity:</p>
58
59<img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 267px; height: 400px;" src="images/all_drawables_changed.png" alt="" id="BLOGGER_PHOTO_ID_5331438978390342066" border="0">
60
61<p>This
62result is explained by the constant state. Even though we are getting a
63new drawable instance for each list item, the constant state remains
64the same and, in the case of BitmapDrawable, the opacity is part of the
65constant state. Thus, changing the opacity of one drawable instance
66changes the opacity of all the other instances. Even worse, working
67around this issue was not easy with Android 1.0 and 1.1.</p>
68
69<p>Android 1.5 and higher offers a very easy way to solve this issue
70with the new {@link android.graphics.drawable.Drawable#mutate()} method</a>.
71When you invoke this method on a drawable, the constant state of the
72drawable is duplicated to allow you to change any property without
73affecting other drawables. Note that bitmaps are still shared, even
74after mutating a drawable. The diagram below shows what happens when
75you invoke <code>mutate()</code> on a drawable:</p>
76
77<img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 307px; height: 400px;" src="images/mutated_states.png" alt="" id="BLOGGER_PHOTO_ID_5331440144116345074" border="0">
78
79<p>Let's update our previous piece of code to make use of <code>mutate()</code>:</p>
80
81<pre>Drawable star = context.getResources().getDrawable(R.drawable.star);
82if (book.isFavorite()) {
83 star.mutate().setAlpha(255); // opaque
84} else {
85 star. mutate().setAlpha(70); // translucent
86}</pre>
87
88<p>For convenience, <code>mutate()</code>
89returns the drawable itself, which allows to chain method calls. It
90does not however create a new drawable instance. With this new piece of
91code, our application now behaves correctly:</p>
92
93<img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 267px; height: 400px;" src="images/correct_drawables.png" alt="" id="BLOGGER_PHOTO_ID_5331440757515573842" border="0">