[Android] Making a custom Android button using a custom view

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    

About these ads

32 Responses to [Android] Making a custom Android button using a custom view

  1. Arun Mankad says:

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

  2. kahgoh says:

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

  3. Arun Mankad says:

    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.

  4. Arun Mankad says:

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

    /]

  5. kahgoh says:

    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.

  6. Hima Bindu says:

    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?

  7. kahgoh says:

    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.

    • Hima Bindu says:

      Thanks a lot for the reply

    • Hima Bindu says:

      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?

      • kahgoh says:

        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.

  8. Hima Bindu says:

    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?

    • kahgoh says:

      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().

  9. Shyam says:

    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.

    • kahgoh says:

      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.

  10. Snowdrop says:

    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?

    • kahgoh says:

      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.

  11. Dolo says:

    I’ve found a good tutorial here periket2000.blogspot.com

  12. Mags says:

    For some reason, the ClickListener class gives me an error always. it appears that this class does not exist anywhere in the android platform nor is it defined in this code file. any suggestions?

  13. kahgoh says:

    Hi,

    I wrote this a fairly long time ago. It is a relatively simple interface, which you can find in this file. It is actually a redundant interface, since the standard View class defines the method setOnClickListener. It pretty much does the same thing.

  14. Kiran Kala says:

    Hi friends..!!

    Pls read carefully…Its useful for u people.,

    We can add normal widgets(buttons, etc ) through XML also,means we can add view through XML also like,

    1. Give layout parameters here (means view parameters, upto where u want)
    2. Here we need to render view class through JAVA(using Canvas)
    3. If we do like this, we need to give to constructors in “MyView” class as follows
    public MyView(Context context)
    {
    super(context);
    }
    public MyView(Context context, AttributeSet attrs)
    {
    super(context, attrs);
    }

    1.Better to give layout parameters
    2. Add ur widgets(like buttons, Edit text fields, etc..,)

    If any doubts let me know pls..actually I faced somany pblms on defining like this…!!

  15. blackbox says:

    How come this man made android view tutorial without made some screenshots for it!
    GEEK!

  16. Temerer says:

    How do you call this view in the code after you write it? for some reason i can’t manage to get it react

  17. kahgoh says:

    Create an instance of the class, set the layout parameters and then add it to your main view. Something like this:

    CustomImageButton button = new CustomImageButton(this,
    	R.drawable.item, "Item");
    LinearLayout.LayoutParams layoutOpt = new LinearLayout.LayoutParams(
    	LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    button.setLayoutParams(layoutOpt);
    

    As noted by Kiran Kala, you can use the standard components that are already available to create your UI using an XML layout or you can also add them programmatically, but if, for some reason, you need to create your own component, you could do it by creating a custom view.

  18. Temerer says:

    I’m not using the xml files because i can’t get the amount of control over the apperance and stuff like that as i can programmaticlly.
    I’m not sure I understood correctly where i sould insert this code, in the activity?

    • kahgoh says:

      If this to be placed on the activity’s content view, I would probably put this code with where I need to create the main view for the activity.

  19. Pingback: Button mit Doppelklick umbenennen - Android-Hilfe.de

  20. hai says:

    Please provide some screenshots. It would be great….

  21. the code formatting plugin you use makes it a pain to copy paste… line numbers always come included

  22. how could we make this button class have the Touch events handled by another class? For example, in my MainActivity, I would like to create several buttons, and assign Methods to handle the interactivity. I don’t want to put the handling code inside the Button class.

    • kahgoh says:

      Define the class that you want to handle the touch event to implement the View.OnTouchListener interface. Then create an instance of your listener and use it as the parameter when calling setOnTouchListener. It should look something like this:

      In the the class that you want to handle the touch event:

      public class Handler implements View.OnTouchListener {
      // ...
          public abstract boolean onTouch (View v, MotionEvent event) {
              // ... (handle the touch event)
          }
      //....
      }
      

      Where ever the button is defined:

      // ...
          // Create and set the handler to the custom class.
          button.setOnTouchListener(new Handler());
      // ...
      

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: