如何将样式选择器颜色应用于自定义按钮子类中的自定义可绘制对象?

How to apply styleable selector color to custom drawable in custom Button subclass?

我创建了一个自定义 Button 子类,它执行一些自定义绘图并添加一些附加功能。

现在我想增加在 Button 上显示自定义 drawable/image/icon 的可能性。添加图像没什么大不了的,但是如何根据当前状态为该图标着色不同的颜色呢?

我尝试使用 ColorStateList 为可绘制对象着色当前状态颜色,但它不起作用:可绘制对象始终使用其自身未更改的颜色绘制。

我假设在将可绘制对象转换为位图时未使用应用的滤色器。这个对吗?我该如何解决这个问题?

这是我的代码:

布局

<com.example.UI.MyButton
     ...
     mc:iconSrc="@drawable/someIcon"
     mc:iconTintColor="@drawable/button_selector_colors"/>

attrs.xml

<declare-styleable name="MyButton">
    <attr name="iconSrc" format="reference" />
    <attr name="iconTintColor" format="reference" />
</declare-styleable>  

button_selector_colors.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true"  android:color="@color/myPressed"/>
    <item android:state_selected="true" android:color="@color/mySelected"/>
    <item android:color="@color/myDefault"/>
</selector>

MyButton.java

public class MyButton extends Button {
    private Drawable mIconDrawable;
    private ColorStateList mIconTintColor;

    public MyButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }

    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyButton);

        for (int i = 0; i < array.getIndexCount(); ++i) {
            int attr = array.getIndex(i);
            switch (attr) {
                case R.styleable.MyButton_iconSrc:
                    mIconDrawable = array.getDrawable(attr);
                    break;
                case R.styleable.MyButton_iconTintColor:
                    mIconTintColor = array.getColorStateList(attr);
                    break;
                default:
                    break;
            }
        }

        array.recycle();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        ...

        if (mIconDrawable != null) {
            ... // calc position, etc.

            if (mIconTintColor != null) {
                mIconDrawable.mutate();
                int[] stateSet = getDrawableState();
                int defaultColor = Color.BLACK; //mIconTintColor.getDefaultColor();

                int stateColor = mIconTintColor.getColorForState(stateSet, defaultColor);

                mIconDrawable.setColorFilter(stateColor, PorterDuff.Mode.SRC_ATOP);
            }

            Bitmap icon = drawableToBitmap(mIconDrawable);
            canvas.drawBitmap(icon, ...);
        }
    }

    public static Bitmap drawableToBitmap (Drawable drawable) {
        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable)drawable).getBitmap();
        }

        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }
}

您看到的错误 "more restores than saves" 表示您调用 Canvas.restore() 的次数比 Canvas.save() 多。每次保存都必须有匹配的恢复。

您没有在代码中显示保存和恢复,但确保它们在所有执行路径中保持平衡。


下溢问题已解决。我认为您遇到了 sizing/placement 问题。不要将按钮的图标转换为位图,而是尝试以下操作:

mIconDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
mIconDrawable.draw(canvas);

您可能无法进行转换。如果你真的想创建位图,我认为固有的宽度和高度不是你所需要的。尝试以下操作:

public static Bitmap drawableToBitmap(Drawable drawable, int width, int height) {
    if (drawable instanceof BitmapDrawable) {
        return ((BitmapDrawable) drawable).getBitmap();
    }

    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
    drawable.draw(canvas);

    return bitmap;
}

然后调用绘制图标如下:

Bitmap icon = drawableToBitmap(mIconDrawable, canvas.getWidth(), canvas.getHeight());
canvas.drawBitmap(icon, 0, 0, null);