Update a view’s background correctly in Android

credit — tutorialspoint.com

One of the common UI requirement is to change background color of a view at runtime. Usually the first thought is to set background color.

View provides the following methods.

setBackgroundColor(int color)
setBackground(Drawable background)
setBackgroundDrawable(Drawable background) (deprecated)

Internally, the background color is presented as a ColorDrawable. Therefore, the easy choice is to use setBackgroundColor

Stop!, something is wrong here.

In ICS, there is a bug with the setBackgroundColor method. 
Here is how the method is implemented in Ice Cream Sandwich

public void setBackgroundColor(int color) {
if (mBGDrawable instanceof ColorDrawable) {
((ColorDrawable) mBGDrawable).setColor(color);
} else {
setBackgroundDrawable(new ColorDrawable(color));

Find the problem? No? Everything looks fine?

The problem is with the ColorState object in the drawable. The ColorDrawable can use ColorState with other ColorDrawable!!!! When you change it, you can change the color of other ColorDrawable!

How it shall work? Look at the latest implementation of setBackgroundColor

public void setBackgroundColor(int color) {
if (mBackground instanceof ColorDrawable) {
((ColorDrawable) mBackground.mutate()).setColor(color);
} else {
setBackground(new ColorDrawable(color));

You can see. The difference is the mutate() method. ColorDrawable.mutate() will create a new ColorState object just for this ColorDrawable object. Therefore, you can do anything to it without accidentally changing the color of other ColorDrawable.

If you want to support all android versions, I would suggest to implement setBackgroundColor using mutate() to avoid it happening. Because the code before ICS is not optimized, it is best to implement setBackgroundColor using the latest mutate() method.


View Class implementation in ICS
View Class implementation after ICS
Discussion about bugs caused by ColorDrawable sharing ColorState