Android Selector Drawable with VectorDrawables srcCompat
Asked Answered
S

6

71

I'm facing a problem with the new backward compatibility with VectorDrawables. In the Support Library 23.2 was a new feature for backward compatibility with Android VectorDrawables indroduced.

I have an ImageView which is a SelectorDrawable assigned to. This Drawable holds several VectorDrawables so I thought I should use app:srcCompat for compatibility. But it does not work on my Galaxy S2 with android 4.1.2.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_gps_fixed_24dp"android:state_activated="true" android:state_selected="true"></item>
    <item android:drawable="@drawable/ic_gps_not_fixed_24dp" android:state_activated="true" android:state_selected="false"></item>
    <item android:drawable="@drawable/ic_gps_not_fixed_24dp" android:state_activated="false" android:state_selected="true"></item>
    <item android:drawable="@drawable/ic_gps_off_24dp" android:state_activated="false" android:state_selected="false"></item>
    <item android:drawable="@drawable/ic_gps_not_fixed_24dp"></item>
</selector>

All drawables are vector xml files.

When using this SelectorDrawable with srcCompat I get this error:

  Caused by: android.content.res.Resources$NotFoundException: File res/drawable/  Caused by: android.content.res.Resources$NotFoundException: File res/drawable/ic_gps_fixed_24dp.xml from drawable resource ID #0x7f0201c1
                                                                           at android.content.res.Resources.loadDrawable(Resources.java:1951)
                                                                           at android.content.res.Resources.getDrawable(Resources.java:672)
                                                                           at android.graphics.drawable.StateListDrawable.inflate(StateListDrawable.java:173)
                                                                           at android.graphics.drawable.Drawable.createFromXmlInner(Drawable.java:881).xml from drawable resource ID #0x7f0201c1

using android:src is even worse.

If I use one of the vector drawables with app:srcCompat all works fine. So I guess it's a problem with the SelectorDrawable and compatibility.

Has anyone had the same problem and found a solution or is it currently not possible to use VectorDrawables in SelectorDrawables prior to Android 5?

The Quick Facts:

  • Compile Target API 23
  • Support Libraray 23.3.0
  • vectorDrawables.useSupportLibrary = true
  • Gradle 2.0
Sears answered 20/4, 2016 at 10:29 Comment(3)
Support for loading vector drawables from resources was removed in version 23.3 - plus.google.com/+AndroidDevelopers/posts/iTDmFiGrVneMidpoint
But: "Using app:srcCompat and setImageResource() continues to work" so app:srcCompat should still work in 23.3. or not?Sears
Yes using app:srcCompat still works so you can set one VectorDrawable to an ImageView. However loading the drawables in an xml state list no longer works unfortunatelyMidpoint
S
70

Some things have changed since I asked this question, so I will answer it myself.

With Support Library 23.4.0 the support for VectorDrawables from Ressources was reenabled: Android Support Library 23.4.0 available now

You can find more information on that in this cast from the Google I/O 2016: What's new in the support library - Google I/O 2016

You need to add this to every Activity where you want to use VectorDrawables on devices below Android 5.0 (Codename Lollipop, API level 21):

static {
    AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}

So you can now use VectorDrawables in DrawableContainers but it can still cause some issues as mentioned in the sources above so use it with caution.

I did not reenable this feature in my app so far but I will change a lot of my icons to VectorDrawables with my next major release and will then dive deeper into this topic.

Sears answered 24/6, 2016 at 11:53 Comment(6)
Also, the following bug report might be of help: code.google.com/p/android/issues/…Breathtaking
"You need to add this to every Activity where you want to use VectorDrawables on devices below Android 5" is not true, we need that even for android 15Washy
@Washy Android 5 equals API level 21. Do not confuse release versions with API versions.Protectorate
where do you use AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); ?I've used it but I am now getting the error XmlPullParserException: Binary XML file line #4: invalid drawable tag vectorPiecework
@Protectorate Well at least call it android 5.0. Android 5 is ambiguous.Hoashis
@Hoashis I was clarifying what the op wrote in his answer. I also just edited the answer to include that clarification.Protectorate
A
65

As @Jahnold mentioned in the comment to question, support for loading vector drawable from an xml state xml list was removed in 23.3.

However, I found several approaches that can help.

1. Using Tint

The approach is suitable if the drawables from selected state list difference only by colors.

First, create only one vector drawable with tint and white fillColor:

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tintMode="multiply"
    android:tint="@color/button_tint">

    <path
        android:fillColor="#ffffff"
        android:pathData="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>

    <path
        android:pathData="M0 0h24v24H0z"/>

</vector>

Second, create color state list button_tint.xml placed in res/color

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="#555555" android:state_enabled="false"/>
    <item android:color="#6699dd"/>
</selector>

Don't forget add follow lines to build.gradle or the approach will not work on old Android versions.

defaultConfig {
    vectorDrawables.useSupportLibrary = true
}

2. Hardcode creating StateListDrawable

The approach is suitable if you use for the state list vector drawables which difference not only a color but also by a figure so you need create several different xml files. Then you can create StateListDrawable programmatically as shown in an answer.

Ammonal answered 27/4, 2016 at 9:49 Comment(4)
I spend an hour to find that the button_tint.xml file should be placed under /res/color folder. So, don't forget it.Fabriane
You probably shouldn't be defining the tint and tint mode in the drawable itself. This limits the drawables reusability. If the designer decides to change the color, or have the same icon but in different colors, you'd need to duplicate the drawable instead of just tinting it using a style etcImpellent
@Ghristopher Perry In this case you can use the second approach.Ammonal
This works! Make sure to use android:tint, and not app:tint.Sweatt
C
7

After watching What's new in the support library - Google I/O 2016 I noticed one useful method in the AppCompatResources class. This is AppCompatResources#getColorStateList(Context context, int resId). With a help of this method I've implemented selector with vector drawables. Here is my color selector file icon_selector:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/red_selected" android:state_selected="true"/>
    <item android:color="@color/red_pressed" android:state_pressed="true"/>
    <item android:color="@color/red"/>
</selector>

And there is java method that returns tinted drawable:

private Drawable getTintedDrawable(@DrawableRes int drawableId) {
    Drawable drawable;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
        drawable = getResources().getDrawable(drawableId, getTheme());
    } else {
        drawable = getResources().getDrawable(drawableId);
    }
    drawable = DrawableCompat.wrap(drawable);
    DrawableCompat.setTintList(drawable.mutate(), AppCompatResources.getColorStateList(this, R.color.selector_nav_bar_item_ico));
    return drawable;
}

You can use it like shown below

yourImageView.setImageDrawable(getTintedDrawable(R.drawable.ic_vector_image));
Comintern answered 7/11, 2016 at 17:30 Comment(0)
C
4

Working fine with below changes.

static {
 AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}

Added in Application class.
app build.gradle inside defaultConfig

vectorDrawables.useSupportLibrary = true
Crescent answered 3/2, 2018 at 12:21 Comment(1)
In my case I had useSupportLibrary set and yet my app on api 16 crushed because I set drawable state list with svg in it. So, it seems, I have to use both variantsLoudermilk
T
2

I suggest this workaround to make the color change based on state: Set a normal, white VectorDrawable, and have the tint have the color selector.

This was tested to work even on emulator with Android API 16, and it works even if you set "vectorDrawables.useSupportLibrary = true" in gradle, of course.

Example: first view is enabled, and the second is disabled:

enter image description here

MainActivity.kt

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        disabledSendMessageButton.isEnabled = false
    }
}

res/layout/activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false"
    android:clipToPadding="false" android:gravity="center" android:orientation="vertical" tools:context=".MainActivity">

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/sendMessageButton" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:clickable="true" android:focusable="true" android:foreground="?attr/selectableItemBackgroundBorderless"
        android:minWidth="?attr/actionBarSize" android:minHeight="?attr/actionBarSize" android:padding="8dp"
        android:scaleType="centerInside" app:srcCompat="@drawable/ic_baseline_send_24" app:tint="@color/color_selector"
        tools:targetApi="m" />

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/disabledSendMessageButton" android:layout_width="wrap_content"
        android:layout_height="wrap_content" android:clickable="true" android:focusable="true"
        android:foreground="?attr/selectableItemBackgroundBorderless" android:minWidth="?attr/actionBarSize"
        android:minHeight="?attr/actionBarSize" android:padding="8dp" android:scaleType="centerInside"
        app:srcCompat="@drawable/ic_baseline_send_24" app:tint="@color/color_selector" tools:targetApi="m" />
</LinearLayout>

res/color/color_selector.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@android:color/secondary_text_dark" android:state_enabled="false" />
    <item android:color="@color/colorPrimary" />
</selector>

res/drawable/ic_baseline_send_24.xml

<vector android:height="24dp" android:tint="#FFFFFF"
    android:viewportHeight="24.0" android:viewportWidth="24.0"
    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="@android:color/white" android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
</vector>
Tashia answered 6/1, 2020 at 9:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.