具有VectorDrawables srcCompat的Android Selector Drawable

71

我在新的向后兼容性方面遇到了问题,与VectorDrawables相关。 在Support Library 23.2中,引入了一项用于兼容Android VectorDrawables的新功能。

我有一个ImageView,它被赋予了SelectorDrawable。这个Drawable包含了几个VectorDrawables,所以我认为应该使用app:srcCompat进行兼容性处理。但是在我的Galaxy S2上,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>

所有的可绘制对象都是矢量 XML 文件。

当我在使用这个 SelectorDrawable 与 srcCompat 一起时,我遇到了这个错误:

  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

使用android:src甚至更糟。

如果我使用其中一个矢量可绘制对象与app:srcCompat一起使用,则所有工作都正常。因此,我想这是SelectorDrawable和兼容性的问题。

是否有人遇到过同样的问题并找到了解决方法,或者目前在Android 5之前无法在SelectorDrawables中使用VectorDrawables?

快速事实:

  • 编译目标API 23
  • 支持库23.3.0
  • vectorDrawables.useSupportLibrary = true
  • Gradle 2.0

1
从23.3版本开始,已经移除了从资源中加载矢量图的支持。详情请见:https://plus.google.com/+AndroidDevelopers/posts/iTDmFiGrVne - Jahnold
1
但是:“使用app:srcCompat和setImageResource()仍然有效”,所以app:srcCompat在23.3中是否仍然有效? - marilion91
2
是的,仍然可以使用app:srcCompat将一个VectorDrawable设置为ImageView。但是很遗憾,在xml状态列表中加载可绘制对象不再起作用。 - Jahnold
6个回答

70

自从我提出这个问题以来,有些事情发生了变化,所以我会自己回答。

通过支持库23.4.0,重新启用了从资源中支持VectorDrawables:现已提供Android支持库23.4.0

您可以在Google I/O 2016的此视频中找到更多信息: 支持库中的新功能 - Google I/O 2016

您需要将此添加到每个要在Android 5.0以下设备(代号为Lollipop,API级别21)上使用VectorDrawables的Activity中:

static {
    AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}

现在你可以在DrawableContainers中使用VectorDrawables,但正如上面提到的那样,它仍然可能会引起一些问题,所以请谨慎使用。

我目前还没有在我的应用程序中重新启用此功能,但是我将在下一个主要版本中更改许多图标为VectorDrawables,并深入研究此主题。


此外,以下错误报告可能会有所帮助:https://code.google.com/p/android/issues/detail?id=210745&pageId=109201119303080409719 - Paul LeBeau
“你需要在每个想要在 Android 5 及以下设备上使用 VectorDrawables 的 Activity 中添加此代码” 不是正确的,我们甚至需要在 Android 15 上添加此代码。 - stallianz
9
Android 5 等同于 API 级别 21。不要混淆发布版本和 API 版本。 - F43nd1r
3
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); 这个方法在哪里使用?我用了它但现在收到一个错误 XmlPullParserException: Binary XML file line #4: invalid drawable tag vector - Someone Somewhere
1
@F43nd1r 至少称其为Android 5.0吧,Android 5太模糊了。 - hqzxzwb
@hqzxzwb 我在澄清 OP 在他的回答中写的内容。我还刚刚编辑了回答,包括那个澄清。 - F43nd1r

65

正如@Jahnold在评论中提到的,自23.3版起,从xml状态xml列表加载矢量可绘制内容的支持已被删除。

然而,我找到了几种可以帮助的方法。

1. 使用Tint

如果所选状态列表中的可绘制内容仅因颜色而异,则此方法适用。

首先,只创建一个具有色调和白色 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>

其次,创建颜色状态列表button_tint.xml并将其放置在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>

不要忘记添加以下代码到build.gradle文件中,否则这种方法在旧版Android上将无法工作。

defaultConfig {
    vectorDrawables.useSupportLibrary = true
}

2. 硬编码创建StateListDrawable

如果您使用的是状态列表矢量可绘制对象,这些对象除了颜色外还有不同的图形,因此您需要创建多个不同的xml文件,则此方法是适合的。然后,您可以按照答案中所示的方式以编程方式创建StateListDrawable


7
我花了一个小时才找到button_tint.xml文件应该放在/res/color文件夹下。所以,请别忘记它。 - Nguyen Minh Binh
你可能不应该在drawable本身中定义tint和tint mode。这会限制drawable的可重用性。如果设计师决定更改颜色或使用不同颜色的相同图标,则需要复制drawable而不是仅使用样式进行着色。 - Christopher Perry
@Ghristopher Perry 在这种情况下,您可以使用第二种方法。 - Sergei Vasilenko
这个可行!请确保使用android:tint而不是app:tint。 - Eino Gourdin

7

在观看了谷歌2016年I/O大会的支持库有哪些新功能之后,我注意到AppCompatResources类中有一个有用的方法。这个方法是AppCompatResources#getColorStateList(Context context, int resId)。通过这个方法的帮助,我已经实现了具有矢量图形的选择器。这是我的颜色选择器文件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>

还有一个返回带色彩的可绘制对象的Java方法:

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;
}

您可以按照下面所示的方式使用它。
yourImageView.setImageDrawable(getTintedDrawable(R.drawable.ic_vector_image));

4

以下更改后正常运作。

static {
 AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}

添加到应用程序类中。
应用程序构建.gradle在defaultConfig内部。

vectorDrawables.useSupportLibrary = true

在我的情况下,我已经设置了useSupportLibrary,但是我的应用在API 16上崩溃了,因为我在其中设置了带有SVG的可绘制状态列表。因此,看来我必须同时使用两种变体。 - Johnny Five

2
我建议采用以下方法来根据状态改变颜色: 设置一个普通的白色VectorDrawable,然后使用颜色选择器来设置着色色调。
这个方法已经在Android API 16模拟器上测试过,并且即使在gradle中设置了"vectorDrawables.useSupportLibrary = true",它也能正常工作。
例如:第一个视图是启用的,第二个视图是禁用的。

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>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接