Kah – The Developer

Making a custom Android button using a custom view

Posted by: kahgoh on: 13 September, 2008

Creating a custom view is as simple as inheriting from the View class and overriding the methods that need to be overridden. In this example, a custom button is implemented in this way. The button shall feature a labelled image (i.e. an image with text underneath).



1   public class CustomImageButton extends View {
2       private final static int WIDTH_PADDING = 8;
3       private final static int HEIGHT_PADDING = 10;
4       private final String label;
5       private final int imageResId;
6       private final Bitmap image;
7       private final InternalListener listenerAdapter = new InternalListener();
8

The constructor can take in the parameters to set the button image and label.


9       /**
10       * Constructor.
11       *
12       * @param context
13       *        Activity context in which the button view is being placed for.
14       *
15       * @param resImage
16       *        Image to put on the button. This image should have been placed
17       *        in the drawable resources directory.
18       *
19       * @param label
20       *        The text label to display for the custom button.
21       */
22      public CustomImageButton(Context context, int resImage, String label) 
23       {
24           super(context);
25           this.label = label;
26           this.imageResId = resImage;
27           this.image = BitmapFactory.decodeResource(context.getResources(),
28                  imageResId);
29  
30           setFocusable(true);
31           setBackgroundColor(Color.WHITE);
32
33           setOnClickListener(listenerAdapter);
34           setClickable(true);
35       }
36

With the constructor defined, there are a number of methods in the View class that needs to be overridden to this view behave like a button. Firstly, the onFocusChanged gets triggered when the focus moves onto or off the view. In the case of our custom button, we want the button to be “highlighted” when ever the focus is on the button.


37       /**
38       * The method that is called when the focus is changed to or from this
39       * view.
40       */
41       protected void onFocusChanged(boolean gainFocus, int direction,
42                      Rect previouslyFocusedRect)
43       {
44           if (gainFocus == true)
45           {
46               this.setBackgroundColor(Color.rgb(255, 165, 0));
47           }
48           else
49          {
50              this.setBackgroundColor(Color.WHITE);
51          }
52      }
53      

The method responsible for rendering the contents of the view to the screen is the draw method. In this case, it handles placing the image and text label on to the custom view.


54      /**
55       * Method called on to render the view.
56       */
57      protected void onDraw(Canvas canvas)
58      {
59          Paint textPaint = new Paint();
60          textPaint.setColor(Color.BLACK);
61          canvas.drawBitmap(image, WIDTH_PADDING / 2, HEIGHT_PADDING / 2, null);
62          canvas.drawText(label, WIDTH_PADDING / 2, (HEIGHT_PADDING / 2) +
63                  image.getHeight() + 8, textPaint);
64      }
65      

For the elements to be displayed correctly on the screen, Android needs to know the how big the custom view is. This is done through overriding the onMeasure method. The measurement specification parameters represent dimension restrictions that are imposed by the parent view.


 66        @Override
 67        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
 68        {
 69         setMeasuredDimension(measureWidth(widthMeasureSpec),
 70                 measureHeight(heightMeasureSpec));
 71        }
 72    

The call to setMeasuredDimension in the onMeasure method is important. The documentation states that the call is necessary to avoid a IllegalStateException.


 73     private int measureWidth(int measureSpec)
 74     {
 75         int preferred = image.getWidth() * 2;
 76         return getMeasurement(measureSpec, preferred);
 77     }
 78    
 79     private int measureHeight(int measureSpec)
 80     {
 81         int preferred = image.getHeight() * 2;
 82         return getMeasurement(measureSpec, preferred);
 83     }
 84    

To calculate the width and height measurements, I’ve chosen to keep the logic simple by using a simple formula to calculate the dimensions. This simple formula computes the dimensions based on the dimensions of the image. The measureSpec parameter specifies what restrictions are imposed by the parent layout.


 85     private int getMeasurement(int measureSpec, int preferred)
 86     {
 87         int specSize = MeasureSpec.getSize(measureSpec);
 88         int measurement = 0;
 89        
 90         switch(MeasureSpec.getMode(measureSpec))
 91         {
 92             case MeasureSpec.EXACTLY:
 93                 // This means the width of this view has been given.
 94                 measurement = specSize;
 95                 break;
 96             case MeasureSpec.AT_MOST:
 97                 // Take the minimum of the preferred size and what
 98                 // we were told to be.
 99                 measurement = Math.min(preferred, specSize);
100                 break;
101             default:
102                 measurement = preferred;
103                 break;
104         }
105    
106         return measurement;
107     }
108

To make the customised button useful, it needs to trigger some kind of action when it is clicked (i.e. a listener). The view class already defines methods for setting the listener, but a more specialised listener could be better suited to the custom button. For example, the specialised listener could pass back information on the instance of the custom button.


109     /**
110      * Sets the listener object that is triggered when the view is clicked.
111      *
112      * @param newListener
113      *        The instance of the listener to trigger.
114      */
115     public void setOnClickListener(ClickListener newListener)
116     {
117         listenerAdapter.setListener(newListener);
118     }
119    

If the custom listener passes information about this instance of the custom button, it may as well have accessors so listener implementation can get useful information about this custom button.


120     /**
121      * Returns the label of the button.
122      */
123     public String getLabel()
124     {
125         return label;
126     }
127    
128     /**
129      * Returns the resource id of the image.
130      */
131     public int getImageResId()
132     {
133         return imageResId;
134     }
135    

Finally, for our custom button class that is using a custom listener, the custom listener class needs to be defined.


136     /**
137      * Internal click listener class. Translates a view’s click listener to
138      * one that is more appropriate for the custom image button class.
139      *
140      * @author Kah
141      */
142     private class InternalListener implements View.OnClickListener
143     {
144         private ClickListener listener = null;
145    
146         /**
147          * Changes the listener to the given listener.
148          *
149          * @param newListener
150          *        The listener to change to.
151          */
152         public void setListener(ClickListener newListener)
153         {
154             listener = newListener;
155         }
156        
157         @Override
158         public void onClick(View v) 
159         {
160             if (listener != null)
161             {
162                 listener.onClick(CustomImageButton.this);
163             }
164         }
165     }
166    }
167    

Tags:

16 Responses to "Making a custom Android button using a custom view"

the code from line number 37 to 48 seems to be missing can fill that, please send me the source file if you can.

Thanks for pointing that out. I’ve just added the missing lines.

My SDK is android 1.0, and eclipse is showing and error with the type ClickListener, is that OnClickListener(line # 144)?

Can you please tell me how can I use this component in the layout, I tried the below mentioned code, but its not working.

[com.max.testView.view.CustomImageButton
android:layout_width="fill_parent"
android:layout_height="wrap_content"
app:label="@string/hello"

/]

Sorry for taking more than a week to get back to you Arun. I haven’t tried using XML to place the custom button on. I’ve only done it programatically. If you require an example of how to do this, I’ve zipped up the code that I originally wrote for this little tutorial. You can find it here: home.amnet.net.au/~kgoh/AndroidButton.zip Have a look in the AndroidButton class. Don’t worry about ButtonIntent and AndroidMenuButton – there are just a couple classes that I was playing around with.

I have tried the code in the link home.amnet.net.au/~kgoh/AndroidButton.zip

I tried to add some additional features. I am stuck at some points. Could you mail me how the text(font type :custom font type) could be changed for the custom button under the image eg painting,etc).?

Also, could you mail me about changing the state of the button to show it is pressed for example using the onPress() and show the button has been pressed?

Changing the font is as easy as creating a Typeface object and setting the paint to use the font (see Typeface.setTypeface()). I’ll probably write an entry about it when I get the chance to. As for changing the state, you could possibly look at overriding onKeyDown and onKeyUp.

Thanks a lot for the reply

Can we set the clickable area of a button? Could you please tell me where in the code I could add the clickable area if we can set it at all?

I’ve never tried this myself, but I would probably start by looking at overriding onTouchEvent(). It receives a MotionEvent object that has information about the X and Y co-ordinates, so from there I believe you should be able to control whether the event gets processed or not.

Can we make the custom image button (in your code) according to the text we write (for example : Instead of Painting I want to add “This is a button of a painting “) and the entire text should be displayed on the button below the image.

Could you please tell me where and what code should be inserted to extend the button to fit the text?

Firstly, if you read the documentation the method onMeasure() is the method that is called to obtain measurements for contents. In my implementation, this gets delegated to measureWidth() and measureHeight(), so you can modify just two functions to calculate their measurements based on the text. At this stage, I’ve also have not looked into how to obtain the pixel width or height of a text label but this technique might help: http://www.mail-archive.com/android-developers@googlegroups.com/msg18526.html

Secondly, I assume you are asking how to change the text. The custom view actually takes in the text label for the button as an argument to its constructor. If you are talking about the positioning of the text, look at onDraw(). Documentation states:

Method called on to render the view.

It is also here that I draw the text with the call to canvas.drawText().

I tried doing this but I could not set the clickable area. Could you please send me a code for that and where to insert it?

Thanks in advance for your help

kahgoh
10 June, 2009 at 8:45 pm

I’ve never tried this myself, but I would probably start by looking at overriding onTouchEvent(). It receives a MotionEvent object that has information about the X and Y co-ordinates, so from there I believe you should be able to control whether the event gets processed or not.

Like I said, I haven’t tried this myself, therefore I haven’t written the code. I’m merely suggesting a possible way of attempting it, but I haven’t tried, tested or verified it myself. However, to provide a quick summary of what I was thinking of.

Start by creating your own View.OnTouchListener. This will calculate and determine whether the click should be actioned or not.

  public class AreaVeto implements View.OnTouchListener {
    public boolean onTouch(View v, MotionEvent event) {
       // Calculation here. 

      return ... // Use false if click is within area?
    }
  }

For calculating whether the click is within the area, the MotionEvent parameters have getX() and getY() to tell you where the click has occured. Because a calculation is being performed, dealing with buttons of any random shape would be more complex. If you are using an image with trasparent background, perhaps you can try calling Bitmap.getPixel() to get the colour at the location to decide whether it is within the clickable area. Obviously, this also implies that the listener needs to know about the bitmap.

After creating your listener, you need to set it as the touch listener. This is done by calling the View.setOnTouchListener() method. You can call this method from within the customised view class or after your view is created.

Hopefully, this will give you a clearer idea of what I was thinking of.

Can the custom button be freely resizable instead of hard coding it ? Meaning, if the Button caption is not one, but two lines high, how does the background adapt?

I mentioned in a previous reply that the height and width of the view are determined by the call to onMeasure(), which delegates to measureHeight() and measureWidth() so you can change these methods to account for the text. For more information, see this previous comment.

Leave a Reply


  • Controlling UI Components using JGoodies Binding « Kah – The Developer: [...] In Binding with JGoodies Binding, the binding library from JGoodies was introduced with a relatively simple example. The binding [...]
  • kahgoh: I mentioned in a previous reply that the height and width of the view are determined by the call to onMeasure(), which delegates to
  • Snowdrop: Can the custom button be freely resizable instead of hard coding it ? Meaning, if the Button caption is not one, but two lines high, how does the back

Blog Stats

  • 18,296 hits

Pages