Dirk Dougherty | 22558d0 | 2009-12-10 16:25:06 -0800 | [diff] [blame] | 1 | page.title=Drawable Mutations |
Scott Main | 796ce77 | 2011-02-16 10:04:45 -0800 | [diff] [blame] | 2 | parent.title=Articles |
| 3 | parent.link=../browser.html?tag=article |
Dirk Dougherty | 22558d0 | 2009-12-10 16:25:06 -0800 | [diff] [blame] | 4 | @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 |
| 8 | container that is usually associated with a View. For instance, a |
| 9 | {@link android.graphics.drawable.BitmapDrawable BitmapDrawable} is used to display |
| 10 | images, a {@link android.graphics.drawable.ShapeDrawable ShapeDrawable} to draw |
| 11 | shapes and gradients, and so on. You can even combine them to create complex |
| 12 | renderings.</p> |
| 13 | |
| 14 | <p>Drawables allow you to easily customize the rendering of the widgets without |
| 15 | subclassing them. As a matter of fact, they are so convenient that most of the |
| 16 | default Android apps and widgets are built using drawables; there are about 700 |
| 17 | drawables used in the core Android framework. Because drawables are used so |
| 18 | extensively throughout the system, Android optimizes them when they are loaded |
| 19 | from resources. For instance, every time you create a |
| 20 | {@link android.widget.Button Button}, a new drawable is loaded from the framework |
| 21 | resources (<code>android.R.drawable.btn_default</code>). This means all buttons |
| 22 | across all the apps use a different drawable instance as their background. |
| 23 | However, all these drawables share a common state, called the "constant state." |
| 24 | The content of this state varies according to the type of drawable you are |
| 25 | using, but it usually contains all the properties that can be defined by a |
| 26 | resource. In the case of a button, the constant state contains a bitmap image. |
| 27 | This way, all buttons across all applications share the same bitmap, which saves |
| 28 | a lot of memory.</p> |
| 29 | |
| 30 | <p>The following diagram shows what entities are |
| 31 | created when you assign the same image resource as the background of |
| 32 | two different views. As you can see, two drawables are created but they |
| 33 | both 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 |
| 38 | problems when you try to modify the properties of a drawable. Imagine an |
| 39 | application with a list of books. Each book has a star next to its name, totally |
| 40 | opaque when the user marks the book as a favorite, and translucent when the book |
| 41 | is not a favorite. To achieve this effect, you would probably write the |
| 42 | following code in your list adapter's <code>getView()</code> method:</p> |
| 43 | |
| 44 | <pre>Book book = ...; |
| 45 | TextView listItem = ...; |
| 46 | |
| 47 | listItem.setText(book.getTitle()); |
| 48 | |
| 49 | Drawable star = context.getResources().getDrawable(R.drawable.star); |
| 50 | if (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: |
| 57 | all 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 |
| 62 | result is explained by the constant state. Even though we are getting a |
| 63 | new drawable instance for each list item, the constant state remains |
| 64 | the same and, in the case of BitmapDrawable, the opacity is part of the |
| 65 | constant state. Thus, changing the opacity of one drawable instance |
| 66 | changes the opacity of all the other instances. Even worse, working |
| 67 | around 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 |
| 70 | with the new {@link android.graphics.drawable.Drawable#mutate()} method</a>. |
| 71 | When you invoke this method on a drawable, the constant state of the |
| 72 | drawable is duplicated to allow you to change any property without |
| 73 | affecting other drawables. Note that bitmaps are still shared, even |
| 74 | after mutating a drawable. The diagram below shows what happens when |
| 75 | you 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); |
| 82 | if (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> |
| 89 | returns the drawable itself, which allows to chain method calls. It |
| 90 | does not however create a new drawable instance. With this new piece of |
| 91 | code, 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"> |