Android:如何以编程方式创建StateListDrawable

38

I have a GridView to display some objects, and visually each of the objects will have an image icon and a text label. I also want the image icon to have some "push and pop" effect when clicked, that is, when pressed, the image will move a small distance to the bottom right direction, and when released get back to its original position.

The objects (and their image icons) are from some dynamic sources. My intuition is to create a StateListDrawable for each item, which will have two states: pressed or not. For GridView item view, I would use a Button, which can accomodate a Drawable and a label, that perfectly satisfies my requirment.

I defined an item class to wrap up the original object:

public class GridItem<T> {

    public static final int ICON_OFFSET = 4;

    private StateListDrawable mIcon;
    private String mLabel;
    private T mObject;

    public Drawable getIcon() {
        return mIcon;
    }

    public void setIcon(Drawable d) {
        if (null == d) {
            mIcon = null;
        }else if(d instanceof StateListDrawable) {
            mIcon = (StateListDrawable) d;
        } else {
            InsetDrawable d1 = new InsetDrawable(d, 0, 0, ICON_OFFSET, ICON_OFFSET);
            InsetDrawable d2 = new InsetDrawable(d, ICON_OFFSET, ICON_OFFSET, 0, 0);
            mIcon = new StateListDrawable();
            mIcon.addState(new int[] { android.R.attr.state_pressed }, d2);
            mIcon.addState(StateSet.WILD_CARD, d1);
            //This won't help either: mIcon.addState(new int[]{}, d1);
        }
    }

    public String getLabel() {
        return mLabel;
    }

    public void setLabel(String l) {
        mLabel = l;
    }

    public T getObject() {
        return mObject;
    }

    public void setObject(T o) {
        mObject = o;
    }

}

Now the problem is, when I touch a grid item, the icon "moves" quite as I have expected, but it won't restore its original position when my finger lifts up leaving the item.

My question is: how to programmatically create a StateListDrawable equivalent to one inflated from an XML resource like

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true"
          android:drawable="@drawable/image_pressed" />  
    <item android:drawable="@drawable/image_normal" />
</selector>

?


不幸的是,问题似乎与使用InsetDrawable有关,您是否尝试过改用BitmapDrawable?声明为xml的插入可绘制对象是否正常工作?我知道这不是一个解决方案,但也许我们可以诊断一下问题... - wjeshak
是的,@wjeshak,你说得对。我修改了GridItem.setIcon()方法,创建了两个BitmapDrawable而不是InsetDrawable,然后我就可以正常看到状态的变化了。 - Yuan
3个回答

42
如果您的可绘制对象仅为位图,则可以以编程方式绘制它们,现在这应该会有所帮助,但我想知道在此处使用InsetDrawable的问题是什么,基本上要使用准备好的以编程方式绘制的BitmapDrawables,您需要修改您的方法以接受位图 b。
        Bitmap bc1 = Bitmap.createBitmap(b.getWidth() + ICON_OFFSET, b.getHeight() + ICON_OFFSET, Bitmap.Config.ARGB_8888);
        Canvas c1 = new Canvas(bc1);
        c1.drawBitmap(b, 0, 0, null);
        Bitmap bc2 = Bitmap.createBitmap(b.getWidth() + ICON_OFFSET, b.getHeight() + ICON_OFFSET, Bitmap.Config.ARGB_8888);
        Canvas c2 = new Canvas(bc2);
        c2.drawBitmap(b, ICON_OFFSET, ICON_OFFSET, null);

        mIcon = new StateListDrawable();
        mIcon.addState(new int[] { android.R.attr.state_pressed },  new BitmapDrawable(bc2));
        mIcon.addState(StateSet.WILD_CARD, new BitmapDrawable(bc1));

嗨@wjeshak,我更喜欢使用Drawable作为输入/输出接口。我使用了Drawable.setBounds()Drawable.draw(Canvas)方法来向位图的边缘添加“填充”,问题得到了解决。你指引了我正确的方向:) 谢谢! - Yuan

13

我看到答案已经被接受了。如果你想动态地为普通状态和按下状态的按钮分配颜色,那么你只需要调用这个函数:

public static StateListDrawable convertColorIntoBitmap(String pressedColor, String normalColor){


        /*Creating bitmap for color which will be used at pressed state*/
        Rect rectPressed = new Rect(0, 0, 1, 1);

        Bitmap imagePressed = Bitmap.createBitmap(rectPressed.width(), rectPressed.height(), Config.ARGB_8888);
        Canvas canvas = new Canvas(imagePressed);       
        int colorPressed = Color.parseColor(pressedColor);
        Paint paintPressed = new Paint();
        paintPressed.setColor(colorPressed);
        canvas.drawRect(rectPressed, paintPressed);
        RectF bounds = new RectF();
        bounds.round(rectPressed);

        /*Creating bitmap for color which will be used at normal state*/
        Rect rectNormal = new Rect(0, 0, 1, 1);     
        Bitmap imageNormal = Bitmap.createBitmap(rectNormal.width(), rectNormal.height(), Config.ARGB_8888);
        Canvas canvasNormal = new Canvas(imageNormal);
        int colorNormal = Color.parseColor(normalColor);
        Paint paintNormal = new Paint();
        paintNormal.setColor(colorNormal);
        canvasNormal.drawRect(rectNormal, paintNormal);


        /*Now assigning states to StateListDrawable*/
        StateListDrawable stateListDrawable= new StateListDrawable();
        stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, new BitmapDrawable(imagePressed));
        stateListDrawable.addState(StateSet.WILD_CARD, new BitmapDrawable(imageNormal));

        return stateListDrawable;

    }

现在你只需要像下面这样将它设置为你的TextView或Button的背景:

 if(android.os.Build.VERSION.SDK_INT>=16){
        yourbutton.setBackground(convertColorIntoBitmap("#CEF6CE00","#4C9D32"));        

            }else{

yourbutton.setBackgroundDrawable(convertColorIntoBitmap("#CEF6CE00","#4C9D32"));
    }

在这里,您可以看到所有需要动态传递颜色的内容,我们完成了。希望这能帮助到某些人:) 您也可以在这里找到它的要点 :)


4
这是很多代码。你可以使用ColorDrawable()替代BitmapDrawable,避免前三个段落的代码。 - dgmltn

4

我看过之前的答案,但是用 ColorDrawable 我提出了一种更加简短和更好的解决方案。

/**
     * Get {@link StateListDrawable} given the {@code normalColor} and {@code pressedColor}
     * for dynamic button coloring
     *
     * @param normalColor  The color in normal state.
     * @param pressedColor The color in pressed state.
     * @return
     */
    public static StateListDrawable getStateListDrawable(int normalColor, int pressedColor) {
        StateListDrawable stateListDrawable = new StateListDrawable();
        stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, new ColorDrawable(pressedColor));
        stateListDrawable.addState(StateSet.WILD_CARD, new ColorDrawable(normalColor));
        return stateListDrawable;
    }

这将接受已解决的颜色作为整数,并使用ColorDrawable将它们添加到StateListDrawable中。
一旦您拥有了可绘制对象,就可以像这样简单地使用它:
if (android.os.Build.VERSION.SDK_INT >= 16) {
            mButton.setBackground(Utils.getStateListDrawable(ResourceUtils.getColor(R.color.white),
                    ResourceUtils.getColor(R.color.pomegranate)));
        } else {
            mButton.setBackgroundDrawable(Utils.getStateListDrawable(ResourceUtils.getColor(R.color.white),
                    ResourceUtils.getColor(R.color.pomegranate)));
        }

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