Android: Change colour of drawable asset programmatically with animation

If you have ever had to gradually change the colour of a button, image or drawable asset in your application from say… grey to orange, you have two options.

Options

  1. Bundle the same asset in the colour grey and orange, overlay them and fade in one above the other.
  2. Programmatically overlay the image or drawable with the colour you want.

While the purpose of this article is mainly to discuss option 2, option 1 will fulfil most requirements and most developers will find option 1 easier to implement. With option 1, your images can contain gradients, shadows and multiple colours and it will still work. With option 2, it would be very difficult to cater for gradients, shadows and different colours.

Option 1: Overlay 2 images and fade in

We want to change a grey image to orange. In order to do this we will need two images as shown. The images will have to be identical and position one over the other in your layout. We have 2 options here as well. Layout grey above orange and fade out grey when you want to convert the image to orange by animating it’s alpha to 0. The second option is to put the orange image above the grey one and set it’s alpha to zero, then animate its alpha to 1 when you want to turn the image orange.

In this scenario, our layout may look something like this.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<ImageView
android:id="@+id/image_grey"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/test_asset"/>

<ImageView
android:id="@+id/image_orange"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:alpha="0"
android:src="@drawable/test_asset_orange"/>
</RelativeLayout>

As you can see above, the two images should perfectly overlay. The grey image is visible and the orange image has it’s alpha value set to zero meaning that it is not visible.

Now if we want to convert the image to orange, we can use a ValueAnimator to gradually change alpha from zero to one.

mImageGrey = (ImageView) findViewById(R.id.image_grey);
mImageOrange = (ImageView) findViewById(R.id.image_orange);

ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mImageOrange.setAlpha((Float) animation.getAnimatedValue());
}
});

animator.setDuration(1500);
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.setRepeatCount(-1);
animator.start();

This works beautifully from Honeycomb and above and works exactly as expected.

Option 2: Overlay colour on top of image

When going down this route, keep in mind that when you overlay a colour, it will overlay all non transparent pixels. Also, you’ll either have to overlay a Drawable or an ImageView. In this example I overlay an ImageView however with a drawable the concept the largely the same.

This time our layout will contain just one ImageView on which we will overlay colour programmatically.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:id="@+id/image"
android:src="@drawable/test_asset"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

</RelativeLayout>

In order to overlay a colour, we will use an ObjectAnimator to animate the alpha for the colour we want to overlay from zero to one. If the overlay colour has an alpha value set on it, the alpha value will be retained and we will effectively animate from zero to whatever the alpha value of the overlay colour is.

In order for this to work, we will have to extract the solid colour from the given colour and adjust it’s alpha. This means that if we want to animate to orange, we will have to break down the orange color into it’s Alpah, Red, Green and Blue values and adjust the alpha depending on where we are in the transition. The code for this is below.

public void animateImageView(final ImageView v) {
final int orange = getResources().getColor(android.R.color.holo_orange_dark);

final ValueAnimator colorAnim = ObjectAnimator.ofFloat(0f, 1f);
colorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float mul = (Float) animation.getAnimatedValue();
int alphaOrange = adjustAlpha(orange, mul);
v.setColorFilter(alphaOrange, PorterDuff.Mode.SRC_ATOP);
if (mul == 0.0) {
v.setColorFilter(null);
}
}
});

colorAnim.setDuration(1500);
colorAnim.setRepeatMode(ValueAnimator.REVERSE);
colorAnim.setRepeatCount(-1);
colorAnim.start();

}

public int adjustAlpha(int color, float factor) {
int alpha = Math.round(Color.alpha(color) * factor);
int red = Color.red(color);
int green = Color.green(color);
int blue = Color.blue(color);
return Color.argb(alpha, red, green, blue);
}

In the code above, “mul” is a multiplier for the alpha value where zero means the overlay is transparent (we will remove the colour filter) and 1 means that the alpha value is set to the value specified by the colour hex code.

The Porter/Duff mode specifies the behaviour that our overlay will take, whether it’s colour will blend with the image’s colours or whether it will replace it. There are a few different modes here to choose from, if you are interested in what these are, a great explanation of Porter/Duff blend modes can be found here.

Result

Which ever approach you use, the result looks like this.

So which one should I use?

Ultimately, this comes down to your requirements. In my use case above, the results are identical to the extent that I record videos of both the outcomes and couldn't tell the difference between them.

Option 1 can handle images with multiple colours and gradients and can even be used if your image changes slightly. Option 2 does not require you to bundle an extra asset and can be used to change to any colour you want (user could pick a colour from a colour picker for example).

Do you have any advice on how to achieve a similar result? Lets hear it in the comments.

For more Android development article or follow me on LinkedIn, Twitter or Google+.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.