Friday, August 22, 2014

Programmatically Coloring Drawables

    With the introduction of Android L, developers now have an easy way to change the color of drawables to match the theme of their app. Despite this, many developers will need to support earlier Android versions in their apps, so I have decided to share a technique for changing drawable asset colors using Picasso for both local and remote assets. The source code for this example can be found on GitHub.

    The key to this technique is using the Transform interface provided in the Picasso library, which provides a transform method to take a bitmap and return an altered bitmap for use in the Picasso image loading method. This interface can be useful for things like changing the size of an image, or in this case changing the image color. The ColorTransformation class that we will use in this example implements the Transform interface and can either accept a color value in its constructor, or have a color set from a resource value.

public ColorTransformation( int color ) {
    setColor( color );
}

public void setColor( int color ) {
    this.color = color;
}

public void setColorFromRes( Context context, int colorResId ) {
    setColor( context.getResources().getColor( colorResId ) );
}

    The transform method is where the magic happens. We provide a source bitmap (which is passed to the transform by Picasso automagically in its drawable building process) and return an altered version. In this example we create a new drawable from the source bitmap in order to get the dimensions of the image, then create a new bitmap and canvas for that bitmap. We then set the bounds for the drawable and apply a color filter using the SRC_IN version of the PorterDuff algorithm - applying the color on top of the visible portions of the drawable ( if you haven't read up on PorterDuff, it's definitely a worth while and interesting topic ) and then draw the altered drawable onto the canvas of the new bitmap before returning it. We also recycle the source bitmap once it is no longer needed per best practices for managing memory with Bitmaps in 2.3 and lower Android devices.

@Override
public Bitmap transform(Bitmap source) {
    if( color == 0 ) {
        return source;
    }

    BitmapDrawable drawable = new BitmapDrawable(Resources.getSystem(), source );
    Bitmap result = Bitmap.createBitmap( drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888 );
    Canvas canvas = new Canvas( result );
    drawable.setBounds( 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight() );
    drawable.setColorFilter( color, PorterDuff.Mode.SRC_IN );
    drawable.draw(canvas);
    drawable.setColorFilter(null);
    drawable.setCallback(null);

    if( result != source ) {
        source.recycle();
    }

    return result;
}

    One additional point to notice in the ColorTransformation class is that there is a key method provided. This is used by Picasso for retaining transformed drawables in memory, allowing for quicker loading when a drawable is transformed and displayed multiple times, such as in a listview.

@Override
public String key() {
    return "DrawableColor:" + color;
}

    Now that the transform class is built, we can use it to load drawables in our app with specific colors. In MainActivity I have four image views: one that uses an unaltered local drawable, one that loads in and transforms a local drawable, one that loads in an unaltered asset through a URL and one that loads in the same asset via URL, but applies a transform to change its color. The latter three imageviews have their drawables populated like so:

Picasso.with( this )
        .load( R.drawable.ic_star )
        .transform( new ColorTransformation(getResources().getColor( R.color.local_drawable_color ) ) )
        .into( mDrawableTransformedImage );

Picasso.with( this )
        .load( getString( R.string.image_url ) )
        .into( mDrawableUrlImage );

Picasso.with( this )
        .load( getString( R.string.image_url ) )
        .transform( new ColorTransformation( getResources().getColor( R.color.remote_image_color ) ) )
        .into( mDrawableUrlTransformedImage );

    As you can see, the ColorTransform class is created for each drawable and a color is passed to it, and Picasso handles the rest by calling Transform and dealing with memory management/caching behind the scenes. The last part of MainActivity uses a Picasso Target to load a drawable into an object that has callbacks associated with it. This technique is used for colorizing and placing an image into the action bar, as the Picasso.into method does not work with the action bar icon directly. First we declare our Target and the actions it should perform with its drawable once it has been loaded or if it fails:

private Target ActionBarIconTarget = new Target()
{
    @Override
    public void onBitmapLoaded( Bitmap bitmap, Picasso.LoadedFrom from )
    {
        getSupportActionBar().setIcon( new BitmapDrawable( getResources(), bitmap ) );
    }

    @Override
    public void onBitmapFailed( Drawable errorDrawable )
    {
        getSupportActionBar().setIcon( R.drawable.ic_launcher );
    }

    @Override
    public void onPrepareLoad( Drawable placeHolderDrawable )
    {

    }
};

    Once we have our target and what it should do defined, we can load our altered drawable into the target so that the image can be loaded into the action bar.

Picasso.with( this )
    .load( R.drawable.ic_star )
    .transform( new ColorTransformation( getResources().getColor( R.color.action_bar_icon_color ) ) )
    .into( ActionBarIconTarget );

    And with that we now have an easy way to load recolored drawables across our app without generating new assets in each color. This is particularly useful when applying techniques like my last post on using Gradle to reskin the same code base for multiple applications. Below I have provided an image from the demo app showing the same icons in various colors after being run through this code. Hopefully it will help you other developers out there save a fair bit of time.

The unaltered URL drawable may be a bit hard to see, but it's originally a black icon of the Android logo

4 comments:

  1. Great post. Very informative and helpful. Thanks for posting. Is there anyway to do something similar for a state selector drawable? Or would that have to be coded manually?

    ReplyDelete
    Replies
    1. Yeah I ran into the issue of wanting to use a state drawable a few times as well. There isn't a very good solution to this, as Picasso isn't set up for it ( https://github.com/square/picasso/issues/497 ). In my case I wanted to have different colored drawables in a navigation drawer with a selected state, so I simply had the adapter in the drawer listen for a selection event, and call notifiyDataSetChanged - then on getView, I checked to see if the current item being drawn was selected, and set its color accordingly.

      Delete
  2. How about state list drawables for selected state, active state, disable state, enabled state etc..? If we can crack this approach for state list drawables, then we can use this solution for all Android Theming worries.

    ReplyDelete
    Replies
    1. See the reply to Vincent ( it's the same answer, so why retype it? :) )

      Delete