Android: 使用DrawableCompat进行着色

52

我正在尝试在 Android API 版本 21 之前对图像进行着色。我已经成功地使用以下代码对项目进行了着色:

<android:tint="@color/red"/>

然而,我似乎无法通过代码在 ImageView 上实现此操作:

Drawable iconDrawable = this.mContext.getResources().getDrawable(R.drawable.somedrawable);
DrawableCompat.setTint(iconDrawable, this.mContext.getResources().getColor(R.color.red));
imageView.setImageDrawable(iconDrawable);

我尝试过设置TintMode,但似乎没有任何变化。我是否错误地使用了v4兼容类DrawableCompat?


2
我成功地通过应用ColorFilter来实现我想要的效果,使用的是SRC_IN模式,我认为这意味着它只是将alpha通道乘以颜色 - 这正是我想要的色调效果:setColorFilter(this.mContext.getResources().getColor(R.color.red), PorterDuff.Mode.SRC_IN)。 - Hippopatimus
8个回答

127

如果有人需要使用DrawableCompat的着色功能而不影响其他可绘制对象,以下是使用mutate()完成此操作的方法:

Drawable drawable = getResources().getDrawable(R.drawable.some_drawable);
Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
wrappedDrawable = wrappedDrawable.mutate();
DrawableCompat.setTint(wrappedDrawable, getResources().getColor(R.color.white));

可以简化为:

Drawable drawable = getResources().getDrawable(R.drawable.some_drawable);
drawable = DrawableCompat.wrap(drawable);
DrawableCompat.setTint(drawable.mutate(), getResources().getColor(R.color.white));

24
mutate()是正确的做法。如果没有使用它,你会为整个应用程序全局着色可绘制对象。 引用javadoc: “可变的可绘制对象保证不与其他可绘制对象共享状态。当您需要修改从资源加载的可绘制对象的属性时,这尤其有用。默认情况下,从同一资源加载的所有可绘制对象实例共享一个公共状态;如果您修改一个实例的状态,则所有其他实例将接收相同的修改。” - Brett
3
适用于4.1及以上版本(API 16及以上)。已使用最新的支持库进行测试(23.1.1)。至少在4.1中需要执行“mutate”。适用于21和23。 - fpanizza
3
如果您需要使用选择器,请改用DrawableCompat.setTintList() - Ionoclast Brigham
如果你使用这个,drawable tint会在全局范围内改变,无法恢复。我使用的一个库使用了这段代码,它会在每个Activity中全局改变我的drawable的颜色。@Brett 你错了。 - Jemshit Iskenderov
1
getResources().getColor(int res) - is deprecated, to get a color with just a Context use ContextCompat.getColor(context, R.color.my_color); - Kirill Karmazin

55

以前,DrawableCompat 不支持着色。从支持库版本 22.1 开始,你可以这样做,但需要按照以下方式进行:

Drawable normalDrawable = getResources().getDrawable(R.drawable.drawable_to_tint);
Drawable wrapDrawable = DrawableCompat.wrap(normalDrawable);
DrawableCompat.setTint(wrapDrawable, getResources().getColor(R.color.colorPrimaryLight));

1
我想知道是否有人能够使用mutate() drawable使其工作。 - Zyoo
@Zyoo 我很确定你不再需要它了,但我刚刚在这里发布了一个答案,使用DrawableCompat的着色和mutate()。无论如何,我认为你会喜欢检查一下 :) - Renan Ferrari
2
这个在API 23和API 19上可以工作,但在API 21上不行。 - Etienne Lawlor
我不得不添加一个drawable.invalidateSelf()才能使它正常工作,但除此之外,没有任何问题。 - OferM

49

如果您不需要ColorStateList,则跨平台进行着色的最简单方法是:

drawable.mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN);

在应用滤镜之前,不要忘记对Drawable进行变异。


2
不确定为什么有人对此进行了负投票,但这段代码实际上就是AppCompat在早期Android版本中的底层实现。使用AppCompat API相比使用此代码来说,会使代码变得冗长无比,因此我认为你并没有从中获得很多好处。 - BladeCoder
在 prelollipop 中,尝试多次执行 DrawableComapat.setTint(_, _) 时出现了一些问题。我找到了解决方案。谢谢。 - Ngima Sherpa

30

这里的答案不适用于老版本(SupportLib 23.4.0之前),但我已经发布了一个解决方法,适用于API 17及以上版本:https://dev59.com/x1oV5IYBdhLWcg3wErWD#37434219

以下代码已在API 17、19、21、22、23和N预览版3上进行了测试,并可正常工作:

    // https://dev59.com/C18d5IYBdhLWcg3weiF-#30928051
    Drawable drawable = DrawableCompat.wrap(ContextCompat.getDrawable(context, R.drawable.vector));
    image.setImageDrawable(drawable);

    /*
     * need to use the filter | https://dev59.com/C18d5IYBdhLWcg3weiF-#30880522
     * (even if compat should use it for pre-API21-devices | https://dev59.com/C18d5IYBdhLWcg3weiF-#27812472)
     */
    int color = ContextCompat.getColor(context, R.color.yourcolor);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        DrawableCompat.setTint(drawable, color);

    } else {
        drawable.mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN);
    }

@hardysim DrawableCompat难道不已经为我们执行了这个检查吗?因此名称中包含“...Compat”。来自该类的文档: /**
  • Helper for accessing features in {@link android.graphics.drawable.Drawable}
  • introduced after API level 4 in a backwards compatible fashion. */
- superuserdo
@superuser,正如我在另一篇帖子中提到的(https://dev59.com/x1oV5IYBdhLWcg3wErWD#37434219),即使Compat类声称是一个适用于所有API的解决方案,但我仍然在所有API上遇到了困难。 - hardysim

6
如果您查看DrawableCompat的源代码,您会发现对于任何版本小于21,该方法什么也不做
DrawableCompat的理念似乎只是在旧版本上避免崩溃,而不是提供实际功能。

这是DrawableCompat方法的一个巨大限制,似乎适用于app compat版本> 22.1。 - Jade
11
如果你真正按照使用的类来进行操作,你会发现DrawableWrapper使用颜色过滤器实现了色彩渲染。 - Jake Wharton
@JakeWharton,问题仍然存在,您能否发布如何正确执行此操作的方法?此帖子中的所有答案都不完全有效或过于模糊。 - desgraci

3

使用22.1版本的支持库,您可以使用DrawableCompat来着色可绘制对象。

使用DrawableCompat.wrap(Drawable)和setTint()、setTintList()和setTintMode()方法即可实现:无需创建和维护单独的可绘制对象来支持多种颜色!


3

我会在这里分享我的解决方案,因为它可能能帮助别人节省时间。

我有一个ImageView,使用矢量图作为其源drawable(实际上,它是来自Android Support Library 23.3的Support Vector Drawable from)。所以,首先我将其包装如下:

mImageView.setImageDrawable(DrawableCompat.wrap(mImageView.getDrawable()));

然后我尝试像这样给它应用色调:

DrawableCompat.setTint(
    mImageView.getDrawable(),
    getResources().getColor(R.color.main_color)
);

运气不佳。

我尝试在包装的可绘制对象上调用mutate(),以及在原始可绘制对象上调用-仍然没有成功。invalidate()mImageView上的调用解决了问题。


为什么你没有使用AppCompatImageView? - fdermishin

0
使用Kotlin为视图设置色调和可绘制对象,并使其向后兼容,同时支持上下文的当前主题,而不检查当前SDK版本并避免使用已弃用的方法。
imageView.setImageDrawable(
            ContextCompat.getDrawable(context, R.drawable.somedrawable).apply {
                setTint(ContextCompat.getColor(context, R.color.red))
            })

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