在API29上,setColorFilter已被弃用。

95
我使用以下代码更改VectorDrawable的颜色: mydrawable.getBackground().setColorFilter(color, PorterDuff.Mode.SRC_ATOP) 这个方法虽然已经过时,但仍可使用。文档建议使用以下代码: mydrawable.getBackground().setColorFilter(new BlendModeColorFilter(color, PorterDuff.Mode.SRC_ATOP)) 但是,BlendModeColorFilter仅适用于API29。通过检查过时方法的源代码,我发现它调用了以下代码: new PorterDuffColorFilter() 因此,我继续使用以下代码: mydrawable.getBackground().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)) 颜色设置正常工作。这是Deprecated方法的正确替代品还是我必须在API29上使用BlendModeColorFilter?
谢谢。

您可以使用条件逻辑来检查用户设备是否在API 29及以上,并使用该颜色过滤器,如果条件不满足,则使用先前使用的代码。 - Edric
@Edric,谢谢你的回复。是的,我可以进行检查,但这需要对100多个实例进行检查,或者在静态方法上执行着色,然后将其链接到所有实例。如果使用PorterDuffColorFilter是正确的,我可以轻松进行批量替换。 - Migrata Nomos
如果您只是在 drawable 上应用纯色而不进行任何转换,则可以使用以下两种方式之一对其进行着色:DrawableCompat.setTint(drawable, color) 或者 直接在 xml 中为 ImageView 等元素着色。 - Andy Res
谢谢您的回复,@AndyRes。我很感激。虽然我不是应用色调,而是完全替换颜色。我创建了一个单一的VectorDrawable,并根据活动使用不同的颜色进行着色。 - Migrata Nomos
好的,我不明白为什么这不会起作用。根据文档:setTint() - Drawable的绘制内容将在绘制到屏幕之前与其色调混合。这类似于{@link #setColorFilter(int, PorterDuff.Mode)}。 - Andy Res
对于任何人来说,关于“着色”与“替换”颜色的问题需要注意。如果您使用“MULTIPLY”混合模式对一个完全为白色的可绘制对象进行着色,那么您基本上正在执行与“替换”颜色相同的操作。这里涉及到基本的数学原理:任何颜色乘以白色(例如,任何值乘以1都是该值)就是该颜色。 - Brian Begun
6个回答

147

试试这个:

public class MyDrawableCompat {
    public static void setColorFilter(@NonNull Drawable drawable, @ColorInt int color) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            drawable.setColorFilter(new BlendModeColorFilter(color, BlendMode.SRC_ATOP));
        } else {
            drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
        }
    }
}

还有这个:

MyDrawableCompat.setColorFilter(mydrawable.getBackground(), color);

更新: 只需使用最新版本的core androidx库和这段代码:

mydrawable.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP)

2
感谢您抽出时间编写此方法,shmakova。我很感激。如果无法验证new PorterDuffColorFilter()的正确性,那就只能选择这种方法了。使用它,我必须手动替换每个条目,而我有100多个条目。我希望有人能够解决new PorterDuffColorFilter()的问题。 - Migrata Nomos
5
使用以下一行代码:drawable.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP)来添加一个颜色滤镜。 - Zbarcea Christian
@ZbarceaChristian 你是对的。这是正确的答案。使用if/else仍然会在构建日志中显示警告。 - chitgoks
一些 BlendModeCompat 的值实际上被标记为需要高 API,而 PorterDuff 则不需要。例如 MULTIPLY。这是怎么回事? - android developer
@androiddeveloper,你有没有找到MULTIPLY的解决方案? - Vivek Thummar
@VivekThummar 请看我与 Google 关于这个问题的对话:https://issuetracker.google.com/issues/202172888 - android developer

77

请使用androidx.core:core:1.2.0或者androidx.core:core-ktx:1.2.0

// Java
implementation 'androidx.core:core:1.2.0'
// Kotlin
implementation 'androidx.core:core-ktx:1.2.0'

还有这个:

drawable.setColorFilter(BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP))

8
使用这个替代方案时要小心。我先尝试了这个方法,但使用“Multiply”混合模式结果与mydrawable.getBackground().setColorFilter(color, PorterDuff.Mode.MULTIPLY)不匹配。以下解决方案似乎效果最佳,且保持了相同的预期视觉效果: mydrawable.getBackground().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)) - Brian Begun
1
@BrianBegun
感谢您的通知! 当我阅读BlendModeUtils.obtainPorterDuffFromCompat的源代码时,这显然是正确的。 很抱歉。显然,它似乎与BlendModeCompat.MODULATE具有相同的行为。 映射如下。 case MODULATE: return PorterDuff.Mode.MULTIPLY;BlendModeUtils.java
- Wataru Mukainakano
1
啊,感谢 @WataruMukainakano 的澄清。他们将 MODULATE 映射到 MULTIPLY 是很奇怪的。我不确定为什么他们这样做。无论如何,感谢您填补了这个空白。非常感谢。 - Brian Begun
2
还有一件事要补充。我查看了BlendModeUtils的源代码(位于此处:https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/core/core/src/main/java/androidx/core/graphics/BlendModeUtils.java?source=post_page---------------------------%2F%2F%2F%2F%2F%2F%2F&autodive=0%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F看起来,对于API29(仅限),MULTIPLY使用BlendMode映射到MULTIPLY,否则对于所有其他API,MODULATE使用PorterDuff.Mode映射到MULTIPLY - Brian Begun
5
谷歌是否与某人签订了协议来打扰我们?它为什么要把一切都弄得那么复杂呢? - Amir Hossein Ghasemi
显示剩余3条评论

14

感谢 @shmakova,我添加了 Kotlin 的解决方案。


import android.graphics.BlendMode
import android.graphics.BlendModeColorFilter
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import android.os.Build
import androidx.annotation.RequiresApi

fun Drawable.setColorFilter(color: Int, mode: Mode = Mode.SRC_ATOP) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        colorFilter = BlendModeColorFilter(color, mode.getBlendMode())
    } else {
        @Suppress("DEPRECATION")
        setColorFilter(color, mode.getPorterDuffMode())
    }
}

// This class is needed to call the setColorFilter 
// with different BlendMode on older API (before 29).
enum class Mode {
    CLEAR,
    SRC,
    DST,
    SRC_OVER,
    DST_OVER,
    SRC_IN,
    DST_IN,
    SRC_OUT,
    DST_OUT,
    SRC_ATOP,
    DST_ATOP,
    XOR,
    DARKEN,
    LIGHTEN,
    MULTIPLY,
    SCREEN,
    ADD,
    OVERLAY;

    @RequiresApi(Build.VERSION_CODES.Q)
    fun getBlendMode(): BlendMode =
        when (this) {
            CLEAR -> BlendMode.CLEAR
            SRC -> BlendMode.SRC
            DST -> BlendMode.DST
            SRC_OVER -> BlendMode.SRC_OVER
            DST_OVER -> BlendMode.DST_OVER
            SRC_IN -> BlendMode.SRC_IN
            DST_IN -> BlendMode.DST_IN
            SRC_OUT -> BlendMode.SRC_OUT
            DST_OUT -> BlendMode.DST_OUT
            SRC_ATOP -> BlendMode.SRC_ATOP
            DST_ATOP -> BlendMode.DST_ATOP
            XOR -> BlendMode.XOR
            DARKEN -> BlendMode.DARKEN
            LIGHTEN -> BlendMode.LIGHTEN
            MULTIPLY -> BlendMode.MULTIPLY
            SCREEN -> BlendMode.SCREEN
            ADD -> BlendMode.PLUS
            OVERLAY -> BlendMode.OVERLAY
        }

    fun getPorterDuffMode(): PorterDuff.Mode =
        when (this) {
            CLEAR -> PorterDuff.Mode.CLEAR
            SRC -> PorterDuff.Mode.SRC
            DST -> PorterDuff.Mode.DST
            SRC_OVER -> PorterDuff.Mode.SRC_OVER
            DST_OVER -> PorterDuff.Mode.DST_OVER
            SRC_IN -> PorterDuff.Mode.SRC_IN
            DST_IN -> PorterDuff.Mode.DST_IN
            SRC_OUT -> PorterDuff.Mode.SRC_OUT
            DST_OUT -> PorterDuff.Mode.DST_OUT
            SRC_ATOP -> PorterDuff.Mode.SRC_ATOP
            DST_ATOP -> PorterDuff.Mode.DST_ATOP
            XOR -> PorterDuff.Mode.XOR
            DARKEN -> PorterDuff.Mode.DARKEN
            LIGHTEN -> PorterDuff.Mode.LIGHTEN
            MULTIPLY -> PorterDuff.Mode.MULTIPLY
            SCREEN -> PorterDuff.Mode.SCREEN
            ADD -> PorterDuff.Mode.ADD
            OVERLAY -> PorterDuff.Mode.OVERLAY
        }
}

按照通常的方式使用:

toolbar?.navigationIcon?.setColorFilter(ContextCompat.getColor(this, color)) /* 1 */
progressBar.indeterminateDrawable.setColorFilter(color, Mode.SRC_IN) /* 2 */

我尝试使用BlendModePorterDuff.Mode参数(例如drawable.setColorFilter(color, BlendMode.SRC_ATOP, PorterDuff.Mode.SRC_ATOP))调用setColorFilter方法,但导致运行时异常:

java.lang.NoClassDefFoundError: Failed resolution of: Landroid/graphics/BlendMode;

因此,我们只能从SDK版本29开始使用BlendMode调用任何方法(它是在那里添加的)。我不得不创建带有Mode参数的setColorFilter


我已在一个大项目中进行了测试,并且它成功地工作。我创建了一个单独的 Kotlin 文件,并从那里调用它,这实际上创建了一个替代的 setColorFilter,以便在满足这些参数时运行原始函数。 - TitanKing
@TitanKing,感谢你的反馈!你还需要使用这个扩展方法吗?你知道,我曾经用了几个月,但后来转而使用了Wataru Mukainakano的解决方案。 - CoolMind

2
PorterDuffColorFilter在API Level 1中添加,并且它并未被弃用,此外,PorterDuffColorFilterColorFilter的子类。您可以像以前一样使用它作为setColorFilter参数的类型是ColorFilter,并且不需要传递BlendModeColorFilter。文档可能建议使用BlendModeColorFilter有很多原因。也许他们计划在将来弃用PorterDuffColorFilter(截至2021年,它仍未被弃用),或者它只是表现更好,谁知道...关键是您已经正确使用了它。如果您想防止代码因PorterDuffColorFilter突然被弃用和删除而崩溃,则应该像许多其他人建议的那样添加版本检查。如果您不担心这个问题,那么您就无需这样做,但您必须注意任何未来的更改。

总之,截至2021年,您的代码将在所有版本上运行。

问候
注意:setColorFilter(int color,PorterDuff.Mode mode)已弃用,本答案仅适用于setColorFilter(ColorFilter colorFilter)。

我因为在Android 11上使用PorterDuff尝试获取Toast的背景以自定义颜色而遇到了空指针异常,导致程序崩溃。 - Hamid Habibi
自定义 Toast 视图在 Android 11 上已经被弃用了,与 PorterDuff 没有任何关系。https://developer.android.com/reference/android/widget/Toast#getView() - Osagui Aghedo
getView 返回 null。 我将 toast 的背景颜色自定义针对 Android 9 及以下版本。 仅为 Android 10+ 显示消息。 - Hamid Habibi

0
Kotlin的扩展函数,用于以编程方式设置着色:
/**
 * Sets a tint color on the drawable of the image view with the specified color.
 *
 * @param color The color to use for the color filter.
 */
fun ImageView.setTintColor(@ColorRes color: Int) {
    val colorInt = ContextCompat.getColor(context, color)

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        drawable?.colorFilter = BlendModeColorFilter(colorInt, BlendMode.SRC_ATOP)
    } else {
        @Suppress("deprecation")
        drawable?.setColorFilter(colorInt, PorterDuff.Mode.SRC_ATOP)
    }
}

使用方法:

imageView.setTintColor(R.color.green)

与BlendMode.SRC_ATOP不同,BlendMode.COLOR适用于我。 - Kishan Solanki

-2

混合模式滤镜可能需要更高的API才能工作。

查看这个链接了解更多信息


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