Programmatically tint a Support Vector

Daniel Wilson picture Daniel Wilson · Apr 27, 2016 · Viewed 8.2k times · Source

Android Studio version 2.1, gradle version 2.1.0, please correct me if you spot any misinterpretations :)

I am confused about support vectors in the support library 23.3.0. Specifically what I would like to do is tint an image button programmatically, whose src is defined is a vector drawable. From what I can tell this is not possible on pre-lollipop now.

I have read several related posts about the changes: 23.2.0 announcement and changes:

As of Android Support Library 23.3.0, support vector drawables can only be loaded via app:srcCompat or setImageResource().

Does the above mean that vector xmls can only be used pre-Lollipop via srcCompat or setImageResource(), and therefore cannot be dynamically tinted?

Here is my basic image button:

<ImageButton
    android:id="@+id/nav_header_exit_community_button"
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:layout_alignParentTop="true"
    android:layout_alignParentRight="true"
    android:background="@null"/>

Works on Lollipop and above only:

    Drawable bg = ContextCompat.getDrawable(a, R.drawable.ic_exit_to_app_24dp);
    DrawableCompat.setTint(bg, headerTitleColor);
    exitButton.setImageDrawable(bg);

Attempting this pre-lollipop throws: android.content.res.Resources$NotFoundException: File res/drawable/ic_exit_to_app_24dp.xml from drawable resource ID #0x7f0200bf

Also works on Lollipop and above only

    Drawable bg = ContextCompat.getDrawable(a, R.drawable.ic_exit_to_app_24dp);
    DrawableCompat.setTint(bg, headerTitleColor);
    exitButton.setImageResource(R.drawable.ic_exit_to_app_24dp);

This throws the same error on pre-Lollipop.

However if I remove vectorDrawables.useSupportLibrary = true as pointed out by Ian Lake here, with the intent of having the build tools auto-generate pngs for pre-Lollipop devices, the pngs do not tint on pre-lollipop, so I'm back to square one.

I have also tried specifying the vector via srcCompat and retrieving it programmatically but I don't think I've been able to achieve that, even though it works on post-Lollipop if the vector is specified using src instead.

So the situation for 23.3.0 seems to be:

  • Post-Lollipop: src and srcCompat accept vectors, only src can be retrieved from the view as a drawable for tinting programmatically. Referencing vectors in code is possible using getDrawable, and they can be tinted.

  • Pre-Lollipop: srcCompat only can accept vectors, cannot be retrieved programmatically from the view for tinting. setImageResource can accept vectors, but only if vectorDrawables.useSupportLibrary = false, and tinting does not work. Similarly referencing vectors in code is not possible unless vectorDrawables.useSupportLibrary = false and tinting does not work.

Working on all versions using pngs:

   Drawable bg = ContextCompat.getDrawable(a, R.drawable.ic_nav_exit_community);
   DrawableCompat.setTint(bg, headerTitleColor);
   exitButton.setImageDrawable(bg);

Addendum:

This technique also works on post-Lollipop, but like the others on pre-Lollipop I get the drawable, but no tinting:

    Drawable bg = VectorDrawableCompat.create(a.getResources(), R.drawable.ic_exit_to_app_24dp, null);
    DrawableCompat.setTint(bg, headerTitleColor);
    exitButton.setImageDrawable(bg);

KIND OF SOLUTION:

Thanks to John's answer so far the only fool-proof way I can come up with to tint a support vector is to set a color filter on it - this means the DrawableCompat.setTint() function is seemingly not functional for me if the drawable in question is a support vector. I'm not sure if this is a legit bug, expected behavior or if I'm just doing something wrong!

Here is the solution I'm going with for the moment:

    Drawable bg;
    if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        bg = VectorDrawableCompat.create(a.getResources(), R.drawable.ic_exit_to_app_24dp, null);
        exitButton.setColorFilter(headerTitleColor, PorterDuff.Mode.MULTIPLY);
    }
    else {
        bg = ContextCompat.getDrawable(a, R.drawable.ic_exit_to_app_24dp);
        DrawableCompat.setTint(bg, headerTitleColor);
    }
    exitButton.setImageDrawable(bg);

Answer

pskink picture pskink · Apr 28, 2016

first of all you should use VectorDrawableCompat#create, once you have your Drawable you have to call DrawableCompat#wrap:

Potentially wrap drawable so that it may be used for tinting across the different API levels, via the tinting methods in this class.

so your code would look like this:

ImageView iv = ....
Drawable d = VectorDrawableCompat.create(getResources(), R.drawable.ic_exit_to_app_24dp, null);
d = DrawableCompat.wrap(d);
DrawableCompat.setTint(d, headerTitleColor);
iv.setImageDrawable(d);