When developing for the Android platform it soon becomes apparent that it is often easier and more efficient to create your own views rather than trying to make the standard Android components do what you want. The standard components work very well for simple layouts, but more advanced functionality requires some custom code.
For the upcoming update to our Weatherwise app, I’ve created a custom view for representing UV Index. I think this view is a great example of a custom view implementation so I’ve decided to share it as an example. Below are some important notes to understand when creating a custom view as well as the source code for our custom view.
Important notes:
init(Context context)
method:
All members are instantiated here. Avoid instantiating any objects in OnDraw and OnMeasure. Instantiating is too expensive and can cause an lag in UI response.
OnMeasure
method:
This method is called to determine the size of your view based on constraints from parent views or from defined dimensions in layouts. The system passes in a requested size for the view and a MeasureSpec mode to help you determine what size the view should be. There are three MeasureSpec modes called EXACTLY, AT_MOST, and UNSPECIFIED
. The EXACTLY
mode specifies that the view should be the exact size passed into the OnMeasure
function. The AT_MOST
mode specifies that the view should be no greater than the given size. Finally, the UNSPECIFIED
mode means there is no specific size, so in our example I return the full size of the bitmap in which the view will be drawn (300×300). You MUST call setMeasuredDimension(w,h)
within OnMeasure
!!!
OnSizeChanged
method:
This method is called after OnMeasure to specify the final dimensions of the view. In the example below, I use this function to alter our drawing Rect
so the view will be drawn correctly to the canvas in our OnDraw
method.
OnDraw
method:
This method is called to actually draw our assets to the view’s canvas before it is displayed in the UI.
Custom UVIndexWheelView example:
What the UVIndexWheelView looks like (The “low” text is not part of the custom view, but is placed over it in the layout):
Image may be NSFW.
Clik here to view.
UVIndexWheelView code:
public class UVIndexWheelView extends View {
private final int mMaxUVMarkers = 11; //!< Number of markers on wheel
private final int mDrawBitmapWidth = 300; //!< Default width of view
private final int mDrawBitmapHeight = 300; //!< Default height of view
private float mRotationDegrees = (360.0f/(float)mMaxUVMarkers); //!< Degrees of rotation between markers
//Images
private Bitmap mDarkMarkerBitmap; //!< Dark "off" marker
private Bitmap mLightMarkerBitmap; //!< Light "on" marker
//Drawing
private Bitmap mResultBitmap; //!< Bitmap to draw markers on
private Canvas mResultCanvas; //!< Canvas to draw markers to (to draw to bitmap)
private Matrix mDrawingMatrix; //!< Matrix for rotating markers
private Paint mDrawingPaint; //!< Paint for drawing markers
private Rect mDrawRect; //!< Draw rect for drawing completed wheel to view canvas (needs to be set to size of view)
private int mUVIndex = 0; //!< UV index we are representing with this view
public UVIndexWheelView(Context context) {
super(context);
init(context);
}
public UVIndexWheelView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/*
* Instantiate members, avoid instantiating anything in onDraw or onMeasure methods
*/
private void init(Context context){
//Images
mUVIndex = 1;
mLightMarkerBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.uv_index_white);
mDarkMarkerBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.uv_index_dark);
mResultBitmap = Bitmap.createBitmap(mDrawBitmapWidth, mDrawBitmapHeight, Bitmap.Config.ARGB_8888);
mResultCanvas = new Canvas(mResultBitmap);
mDrawingPaint = new Paint();
mDrawingPaint.setAntiAlias(true);
mDrawingPaint.setDither(true);
mDrawingPaint.setFilterBitmap(true);
mDrawingMatrix = new Matrix();
mDrawRect = new Rect(0, 0, mDrawBitmapWidth, mDrawBitmapHeight);
}
/*
* Drawing of the view to it's canvas
*
* (non-Javadoc)
* @see android.view.View#onDraw(android.graphics.Canvas)
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Clear canvas before redrawing
mResultCanvas.drawColor(Color.TRANSPARENT);
canvas.drawColor(Color.TRANSPARENT);
//Draw each marker
for(int i = 0; i < mMaxUVMarkers; i++){
Bitmap marker = getMarkerBitmap(i);
int centerX = (mDrawBitmapWidth/2) - (marker.getWidth()/2);
int centerY = (mDrawBitmapHeight/2) - (marker.getHeight()/2);
mDrawingMatrix.reset();
mDrawingMatrix.postTranslate(centerX, centerY);
mDrawingMatrix.postTranslate(0, -centerY);
mDrawingMatrix.postRotate(mRotationDegrees*(float)i, mDrawBitmapWidth/2, mDrawBitmapHeight/2);
mResultCanvas.drawBitmap(marker, mDrawingMatrix, mDrawingPaint);
}
//After drawing wheel to result bitmap, draw the result bitmap to the view's canvas.
//This allows the view to be resized to whatever size the view needs to be.
canvas.drawBitmap(mResultBitmap, null, mDrawRect, mDrawingPaint);
}
/*
* Determining view size based on constraints from parent views
*
* (non-Javadoc)
* @see android.view.View#onMeasure(int, int)
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//Get size requested and size mode
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width, height = 0;
//Determine Width
switch(widthMode){
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
width = Math.min(mDrawBitmapWidth, widthSize);
break;
case MeasureSpec.UNSPECIFIED:
default:
width = mDrawBitmapWidth;
break;
}
//Determine Height
switch(heightMode){
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
height = Math.min(mDrawBitmapHeight, heightSize);
break;
case MeasureSpec.UNSPECIFIED:
default:
height = mDrawBitmapHeight;
break;
}
setMeasuredDimension(width, height);
}
/*
* Called after onMeasure, returning the actual size of the view before drawing.
*
* (non-Javadoc)
* @see android.view.View#onSizeChanged(int, int, int, int)
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//Change height and width of draw rect to size of view
//mDrawRect is using to draw the wheel to the view's canvas,
//setting mDrawRect to the width and height of the view ensures
//the wheel is drawn correctly to the view
mDrawRect.set(0, 0, w, h);
}
/*
* Setter for UVIndex
*/
public void setUVIndex(int index){
if(index < 0) index = 0; else if(index > mMaxUVMarkers) index = mMaxUVMarkers;
mUVIndex = index;
invalidate();
}
/*
* Returns correct "on" or "off" bitmap for marker index
*/
private Bitmap getMarkerBitmap(int index){
if (index < mUVIndex){
return mLightMarkerBitmap;
}else{
return mDarkMarkerBitmap;
}
}
}
I hope this example helps anyone having trouble with creating custom views in Android. Of course you should also check out creating custom views from the official Android developer website.
The post Creating a custom view implementation in Android appeared first on Local Wisdom.