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
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"
/]
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?
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?
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.
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?
1 | Arun Mankad
31 October, 2008 at 7:20 pm
the code from line number 37 to 48 seems to be missing can fill that, please send me the source file if you can.