在Android 4上setColorFilter()无法正常工作,在Android 5上运行正常

13

我正在尝试在屏幕上定期闪烁不同颜色(每秒几次)。

为了改变颜色,我在主视图的背景上使用 Drawable.setColorFilter(int color, Mode mode)

  • myView.getBackground().setColorFilter(Color.RED, PorterDuff.Mode.SRC);

出于调试目的,我添加了另一个视图,使用 View.setBackgroundColor(int color) 进行更改。

问题是,setColorFilter() 调用在 Lollipop 上运行正常,但在 早期版本(特别是 Nexus 7 v4.4.4、Galaxy Nexus v4.2.1)上出现问题。


我在一个 Handler 中触发一个 Runnable 来调用颜色更改代码。

无论在哪个平台上,处理程序都会被调用(我可以通过调试中的 setBackgroundColor() 调用看到背景更改)。

以下是变换颜色的代码:

Handler mHandler;
RunnableOnTick thisRunnable;
View vDebug;
View vBroken;

class RunnableOnTick implements Runnable
{
    int backgroundColor;

    @Override
    public void run()
    {
        color = random.nextInt(2);

        switch (color)
        {
            case 0:
            {
                backgroundColor = Color.RED;
                break;
            }
            case 1:
            {
                backgroundColor = Color.GREEN;
                break;
            }
        }

        // this works on all platforms
        vDebug.setBackgroundColor(backgroundColor);

        // this works only on Lollipop
        vBroken.getBackground().setColorFilter(backgroundColor, PorterDuff.Mode.SRC);
        vBroken.invalidate();

        mHandler.postDelayed(thisRunnable, 100);
    }
}

我尝试了不同的PorterDuff.Mode值 - 但在Android 4上无法使其正常工作。

在Android v4和v5之间有什么不同会影响setColorFilter()的工作方式?


你到底使用的是哪个“Drawable”?这可能取决于具体的实现。 - Dmitry Zaytsev
@Dmitry 如果我没记错的话,我只是使用了绿色的Android默认启动器图标,因为这只是一个我随意拼凑的概念证明。最终我使用了另一种方法,并且数月都没有再看过那个应用程序。 - Richard Le Mesurier
然后是 BitmapDrawable。嗯,如果你不再拥有这个应用程序了,那么现在很难找到问题。我可以肯定地说,在 KitKat 中颜色过滤器是起作用的(你可以简单尝试使用 ImageViewtint 属性 - 它由 ColorFilter 支持)。 - Dmitry Zaytsev
@Dmitry 我会看看能从存储库中提取什么。我始终很有兴趣知道错误是出在我身上还是一个非常经过测试的框架上。我也会测试提供的答案代码并进行评论。感谢你的意见。 - Richard Le Mesurier
@Richard 请尝试下面的方法。 - Hardik
7个回答

6

最终,问题似乎在于KitKat不支持在StateListDrawable中使用ColorFilter(或者隐含的alpha)来处理将要使用的Drawable。我的解决方案是使用相同的代码构建复杂的Drawable,然后将其渲染成简单的BitMapDrawable:

 static Drawable createDrawable(Context context, int color, boolean disabled) {
OvalShape oShape = new OvalShape();
ShapeDrawable background = new ShapeDrawable(oShape);
background.getPaint().setColor(color);

ShapeDrawable shader = new ShapeDrawable(oShape);
shader.setShaderFactory(new ShapeDrawable.ShaderFactory() {
    @Override
    public Shader resize(int width, int height) {
        return new LinearGradient(0, 0, 0, height,
                new int[]{
                        Color.WHITE,
                        Color.GRAY,
                        Color.DKGRAY,
                        Color.BLACK
                }, null, Shader.TileMode.REPEAT);
    }
});

Drawable icon = ContextCompat.getDrawable(context, R.drawable.ic_chat_button).mutate();
icon.setColorFilter(context.getResources().getColor(R.color.control_tint_color), PorterDuff.Mode.SRC_IN);

Drawable layer = new LayerDrawable(new Drawable[]{ shader, background, icon });
layer.setAlpha(disabled ? 128 : 255);

// Note that on KitKat, setting a ColorFilter on a Drawable contained in a StateListDrawable
//  apparently doesn't work, although it does on later versions, so we have to render the colored
//  bitmap into a BitmapDrawable and then put that into the StateListDrawable
Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);

layer.setBounds(0, 0, layer.getIntrinsicWidth(), layer.getIntrinsicHeight());
layer.draw(canvas);

return new BitmapDrawable(context.getResources(), bitmap);
}

1
你有关于KitKat不支持它的任何参考资料吗? - Richard Le Mesurier
不,我没有这样的参考资料,但上面的解决方案是有效的...如果有帮助,请给我的答案点赞。 - Harry Sharma
解决了我的问题,不仅适用于KitKat,而且发现它对API < 21也没有起作用,但是通过这种解决方法可以正常运行。获得赏金。 - Jose Gonzalez
这个问题在我的两个三星设备上(4.4和4.3.1)也一直存在。不幸的是,这个修复方法对我无效。 - sud007

3

我在早期的安卓版本中也遇到了同样的问题,我通过替换以下内容来解决:

vBroken.getBackground().setColorFilter(backgroundColor, PorterDuff.Mode.SRC);

使用:

    Drawable d = vBroken.getBackground();
    d.setColorFilter(backgroundColor, PorterDuff.Mode.MULTIPLY);
    vBroken.setBackground(d);

完美的解决方案,必须选择为已接受的解决方案,谢谢 @mengoni - NullPointerException

3

在API 21以下的AppCompat中存在一个与复合Drawable相关的问题,我认为这个问题与以下链接有关:https://code.google.com/p/android/issues/detail?id=191111

简单的解决方法是不要使用XML中的drawables,而是在代码中创建它们,然后应用setColorFilter。这就是为什么@Hardeep的解决方案有效。

有趣的小知识:在我的情况下,对XML创建的TextView drawableLeft应用setColorFilter可以正常工作,但只有在通过单击处理程序/延迟调用时才能正常工作。当在onCreate / onResume等中调用时,什么也不会发生。


哈哈,感谢你的有趣小知识,我花了将近3个小时寻找解决方案,最终在OnClick上发布了一个延迟可运行对象,问题得以解决。 - Flux

1
对于我来说,在StateListDrawable中,ColorFilter不会应用于颜色项。
创建一个表示该颜色的Drawable,并在StateListDrawable中使用它,可以看到setColorFilter在我测试的早期Android设备上工作正常。
顺便说一下,我制作了一个纯白色的Drawable,这样着色颜色就可以以完全不透明度应用。

这是正确的答案。颜色项目不能通过setColorFilter更改,应更改为任何可绘制对象,例如1像素x1像素的可绘制项目。 - Rubén Viguera

0
为了在所有Android版本上绘制可绘制对象的不同状态,您可以使用此辅助类:
public class MagicDrawable extends StateListDrawable {

private Map<Integer, Integer> stateColorsMap;
private int defaultColor;

public static MagicDrawable create(Drawable drawable, int defaultColor, int pressedColor, int selectedColor){
    Map<Integer, Integer> map = new HashMap();
    map.put(android.R.attr.state_selected, selectedColor);
    map.put(android.R.attr.state_pressed, pressedColor);

    MagicDrawable stateDrawable = new MagicDrawable(drawable, defaultColor, map);
    return stateDrawable;
}

public static MagicDrawable createSelected(Drawable drawable, int defaultColor, int selectedColor){
    Map<Integer, Integer> map = new HashMap();
    map.put(android.R.attr.state_selected, selectedColor);

    MagicDrawable stateDrawable = new MagicDrawable(drawable, defaultColor, map);
    return stateDrawable;
}

public static MagicDrawable createPressed(Drawable drawable, int defaultColor, int pressedColor){
    Map<Integer, Integer> map = new HashMap();
    map.put(android.R.attr.state_pressed, pressedColor);

    MagicDrawable stateDrawable = new MagicDrawable(drawable, defaultColor, map);
    return stateDrawable;
}

/**
 *  Create state list drawable programmatically - just pass drawable, default color of drawable, and Map of (state , color)
 * @param drawable resourse for icons
 * @param defaultColor color for normal state
 * @param stateColorsMap map of for ex. android.R.attr.state_pressed and ColorManager.someColor
 *
 *                        Map<Integer, Integer> map = new HashMap<>();
map.put(android.R.attr.state_pressed, ColorManager.rose);
 */
public MagicDrawable(Drawable drawable, int defaultColor, Map<Integer, Integer> stateColorsMap) {
    super();
    this.stateColorsMap = stateColorsMap;
    this.defaultColor = defaultColor;

    drawable.setColorFilter(defaultColor, PorterDuff.Mode.SRC_IN);

    if (stateColorsMap != null) {
        for (int state : stateColorsMap.keySet()) {
            addState(new int[]{state}, drawable);
        }
    }

    addState(new int[] {}, drawable);
}

@Override
protected boolean onStateChange(int[] states) {

    if (stateColorsMap == null) {
        super.setColorFilter(defaultColor, PorterDuff.Mode.SRC_IN);
        return super.onStateChange(states);
    }

    boolean colorSet = false;

    for (int state : states) {

        for (int st : stateColorsMap.keySet()){

            if (state == st) {
                super.setColorFilter(stateColorsMap.get(st), PorterDuff.Mode.SRC_IN);
                colorSet = true;
                break;
            }
        }
    }

    if (!colorSet) {
        super.setColorFilter(defaultColor, PorterDuff.Mode.SRC_IN);
    }

    return super.onStateChange(states);
}

@Override
public boolean isStateful() {
    return true;
}

}


0

你试过直接这样吗?!

vBroken.getBackground().setColorFilter(Color.argb(255, 255, 255, 255),PorterDuff.Mode.MULTIPLY));

-4
/**
     * Tint / Colorise the Supplied {@code drawable} into another color of supplied {@code colorResId}
     * @param context
     * @param drawable
     * @param colorResId
     * @return
     */
    public Drawable tintThisDrawable(Context context ,Drawable drawable,@ColorRes int colorResId)
    {
        Resources res = context.getResources();
        int color = res.getColor(colorResId);
        if (drawable != null) {
            drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
        }
        return drawable;
    }

我记得在我的项目中有这个函数,你可以自己测试一下。


那几乎就是他回答中所写的。 - Dmitry Zaytsev
好的!现在我明白了,它涉及到可绘制对象而不是颜色。如果他可以使用Drawable来设置背景,那么这将起作用。 - Y2K
这正是他正在做的事情。请在回答之前阅读问题。 - Dmitry Zaytsev
此函数适用于KITKAT和LOLLIPOP版本,可对Drawable进行着色。已测试通过! - Y2K
1
即使它能工作,它仍然没有回答问题。 - Dmitry Zaytsev
1
我能看到的唯一区别是我尝试使用View.getBackground().setColorFilter()。也许这个调用和你演示的有所不同?我会在接下来的一周左右再次尝试并研究它。 - Richard Le Mesurier

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