Quantcast
Viewing all articles
Browse latest Browse all 15

Creating a custom view implementation in Android

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.
uvwheel

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.


Viewing all articles
Browse latest Browse all 15

Trending Articles