Android: How to create a StateListDrawable programmatically
Asked Answered
S

3

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>

?

Segmental answered 28/6, 2011 at 4:55 Comment(2)
unfortunately the problem seems to be connected with the usage of InsetDrawable, have you tried to use BitmapDrawable instead? Does inset drawables declared as xml work ok? I know that it isn't a solution but maybe we diagnose the problem...Demodulation
Yes @Demodulation you are right. I modified the GridItem.setIcon() method creating two BitmapDrawables other then InsetDrawables then I can see the states change normallySegmental
D
42

if your drawables are just bitmaps, you could draw them programmatically, for now it should help, however I wonder what is the problem with InsetDrawable usage here, basically use prepared BitmapDrawables that are drawn programatically, you would need to modify your method to accept bitmaps 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));
Demodulation answered 28/6, 2011 at 6:18 Comment(1)
Hi @wjeshak, I prefer Drawable as the Input/Ouput interfaces. I used the Drawable.setBounds() and Drawable.draw(Canvas) methods to add "paddings" to the edges of the bitmaps, and the problem resolved. You pointed me to the right direction:) Thanks!Segmental
Q
13

I can see the answer is already accepted. I am sharing if you want to assign dynamically colors of buttons from the users for normal as well as pressed state. then you can just call this function :

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;

    }

Now all you need is to set it as your textview or button background like below :

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

            }else{

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

Here you can see all you need to pass the colors dynamically and we're done. hope this will help someone :) You can find it's gist too here :)

Quarles answered 22/5, 2014 at 4:58 Comment(1)
That is a lot of code. You could use ColorDrawable() instead of BitmapDrawable and avoid the first three stanzas of that code.Slavism
E
4

I have seen the previous answers, but came up with a much shorter and better solution using 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;
    }

This accepts resolved colors as integer and uses ColorDrawable to add them in a StateListDrawable.

Once you have the drawable, you can use it simply like this,

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)));
        }
Enrollee answered 16/9, 2016 at 11:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.