May 3, 2015

Android: Dynamically Coloring Images with Picasso

I'm working on an app where the server provides certain icon images but only in one color. Needing to tint these images, I checked whether the tinting from Lollipop was backward compatible. Nope. Not in AppCompat. That would be too easy! So I had to find another way.

Spoiler alert: Turns out I didn't technically need Picasso.

The Basic Idea: Use ColorFilters!

Fortunately, next Google pointed me to my friend Paul's awesome blog. He gave an excellent overview on Programmatically Coloring Drawables that's worth a quick read. After adapting his solution to my specific needs, I decided to post about my code so that the next time this comes up, I know exactly what to do:

Picasso.with(viewHolder.itemView.getContext())
 .load(iconUrl)
 .fit()
 .centerInside()
 .into(viewHolder.currentWeatherIcon);

int iconColor = viewHolder.itemView.getResources().getColor(R.color.weather_icon_color);
viewHolder.currentWeatherIcon.setColorFilter(
 new PorterDuffColorFilter(iconColor, PorterDuff.Mode.SRC_IN)
);

The core idea was to apply a ColorFilter to the ImageView but, in my case, since I already had an ImageView, I didn't have to go the route of using a Transformer. Instead, I can just apply the ColorFilter to my ImageView, directly. Also, I could leverage a PorterDuffColorFilter since I knew I always want to use Porter Duff Compositing here.

One Step Further: Leverage the ViewHolder!

Given the way a ColorFilter works, I figured this could also be set on the ViewHolder, itself, during construction. So my final, working solution was along the lines of this:

public static class ViewHolder extends RecyclerView.ViewHolder {
    /* [other view holder stuff here] */
    public ImageView currentWeatherIcon;

    public ViewHolder(View v) {
        super(v);
        /* [other view holder stuff here] */
        currentWeatherIcon = (ImageView) v.findViewById(R.id.current_weather_icon);
        currentWeatherIcon.setColorFilter(
                new PorterDuffColorFilter(v.getResources().getColor(
                    R.color.weather_icon_color), PorterDuff.Mode.SRC_IN)
        );
    }
}

// elsewhere in code...
Picasso.with(viewHolder.itemView.getContext())
        .load(iconUrl)
        .fit()
        .centerInside()
        .into(viewHolder.currentWeatherIcon);

Of course, if I needed different icon colors, based on the data, then I'd have to set the color filter inside of onBindViewHolder but in my case, the icon color is always the same so this worked well. Bonus: this solution ended up not even requiring Picasso. I know I'll be coming back to this post in 8-12 months, after I've forgotten half this stuff...

Good thing I wrote it down!

No comments:

Post a Comment