Tuesday, March 6, 2012

Creating Custom Buttons in Android on the Fly

I needed to be able to set the border color of a button or the "stroke" as it's referred to in android on in the code and not the xml, because it was based on a color received from a web service. Everywhere I looked people were easily creating custom buttons with xml, but these values, at least with what I played with could not be changed on the fly.

Now another big issue is that the xml for shapes does not seem to correspond 1 to 1 with their code counter parts. I can easily create a rectangle with one color as the boarder and another color as the fill in xml not so easy in the code.
Here is the solution I came up with for my custom button in code.
First I created a custom shape class and override the onDraw method.


private class CustomRect extends ShapeDrawable
{
    Paint fillpaint, strokepaint;
    private static final int STROKE_WIDTH = 10;

    public customRect(Shape s)
    {
        super(s);
        fillpaint = this.getPaint();
        strokepaint = new Paint(fillpaint);
        strokepaint.setStyle(Style.STROKE);
        strokepaint.setStrokeWidth(STROKE_WIDTH);
        fillpaint.setAlpha(128);
    }

    @Override
    protected void onDraw(Shape shape, Canvas canvas, Paint fillpaint)
    {
        shape.draw(canvas, fillpaint);
       shape.draw(canvas, strokepaint);
    }

    public void setStrokeColor(int c)
    {
       strokepaint.setColor(c);
    }

    public void setFillColor(int c)
    {
       fillpaint.setColor(c);
       fillpaint.setAlpha(128);
    }
}


This class is geared very specifically to how I wanted the rectangle to be but should be a good starting point to anyone else trying to do the same.

Next create the shapes you want to use for your button.


Final CustRect cust_rect_enabled = new CustRect(new RectShape());
cust_rect_enabled.setStrokeColor(myColor1);
cust_rect_enabled.setFillColor(myColor2);



Final CustRect cust_rect_pressed = new CustRect(new RectShape());
cust_rect_pressed.setStrokeColor(myColor2);
cust_rect_pressed.setFillColor(myColor1);


Now you want to add these Drawables to a StateListDrawable, in order to handle what the button looks like when it is pressed.


StateListDrawable state_list_drawable = new StateListDrawable();
state_list_drawable.addState(new int[] {android.R.attr.state_pressed}, cust_rect_pressed);
state_list_drawable.addState(StateSet.WILD_CARD, cust_rect_enabled);


This will tell the button what to use depending on what state it is in. For everything but the pressed event it will be the cust_rect_enabled, when pressed it will be cust_rect_pressed. This works for what I need it to, but I would image other projects would need other states to be handled as well. You can find the other states you may want to set in android.R.attr, the ones you will want to use will generally start with state_. I did play around with a few of these and was having some issues with getting them to do what I wanted, it is very different than the states in the xml setting them to true and false.

Lastly you want to add a default regular android button to the layout file you are using, and set the height and width to what you want it as. Then in the code you get your handle to it the usual way of calling findViewById, and set the background Drawable.


Button myCustButton = (Button) findViewById(R.id.buttonQuizNextQuestion);
myCustButton.setBackgroundDrawable(state_list_drawable);


And that is all there is to it!