1 page.title=Drawable Mutations 2 parent.title=Articles 3 parent.link=../browser.html?tag=article 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"> 94