page.title=Custom Drawing parent.title=Creating Custom Views parent.link=index.html trainingnavtop=true previous.title=Creating a View Class previous.link=create-view.html next.title=Making the View Interactive next.link=making-interactive.html @jd:body

This lesson teaches you to

  1. Override onDraw()
  2. Create Drawing Objects
  3. Handle Layout Events
  4. Draw!

You should also read

Try it out

Download the sample

CustomView.zip

The most important part of a custom view is its appearance. Custom drawing can be easy or complex according to your application's needs. This lesson covers some of the most common operations.

Override onDraw()

The most important step in drawing a custom view is to override the {@link android.view.View#onDraw(android.graphics.Canvas) onDraw()} method. The parameter to {@link android.view.View#onDraw(android.graphics.Canvas) onDraw()} is a {@link android.graphics.Canvas Canvas} object that the view can use to draw itself. The {@link android.graphics.Canvas Canvas} class defines methods for drawing text, lines, bitmaps, and many other graphics primitives. You can use these methods in {@link android.view.View#onDraw(android.graphics.Canvas) onDraw()} to create your custom user interface (UI).

Before you can call any drawing methods, though, it's necessary to create a {@link android.graphics.Paint Paint} object. The next section discusses {@link android.graphics.Paint Paint} in more detail.

Create Drawing Objects

The {@link android.graphics} framework divides drawing into two areas:

For instance, {@link android.graphics.Canvas Canvas} provides a method to draw a line, while {@link android.graphics.Paint Paint} provides methods to define that line's color. {@link android.graphics.Canvas Canvas} has a method to draw a rectangle, while {@link android.graphics.Paint Paint} defines whether to fill that rectangle with a color or leave it empty. Simply put, {@link android.graphics.Canvas Canvas} defines shapes that you can draw on the screen, while {@link android.graphics.Paint Paint} defines the color, style, font, and so forth of each shape you draw.

So, before you draw anything, you need to create one or more {@link android.graphics.Paint Paint} objects. The {@code PieChart} example does this in a method called {@code init}, which is called from the constructor:

private void init() {
   mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   mTextPaint.setColor(mTextColor);
   if (mTextHeight == 0) {
       mTextHeight = mTextPaint.getTextSize();
   } else {
       mTextPaint.setTextSize(mTextHeight);
   }

   mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   mPiePaint.setStyle(Paint.Style.FILL);
   mPiePaint.setTextSize(mTextHeight);

   mShadowPaint = new Paint(0);
   mShadowPaint.setColor(0xff101010);
   mShadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL));

   ...

Creating objects ahead of time is an important optimization. Views are redrawn very frequently, and many drawing objects require expensive initialization. Creating drawing objects within your {@link android.view.View#onDraw(android.graphics.Canvas) onDraw()} method significantly reduces performance and can make your UI appear sluggish.

Handle Layout Events

In order to properly draw your custom view, you need to know what size it is. Complex custom views often need to perform multiple layout calculations depending on the size and shape of their area on screen. You should never make assumptions about the size of your view on the screen. Even if only one app uses your view, that app needs to handle different screen sizes, multiple screen densities, and various aspect ratios in both portrait and landscape mode.

Although {@link android.view.View} has many methods for handling measurement, most of them do not need to be overridden. If your view doesn't need special control over its size, you only need to override one method: {@link android.view.View#onSizeChanged onSizeChanged()}.

{@link android.view.View#onSizeChanged onSizeChanged()} is called when your view is first assigned a size, and again if the size of your view changes for any reason. Calculate positions, dimensions, and any other values related to your view's size in {@link android.view.View#onSizeChanged onSizeChanged()}, instead of recalculating them every time you draw. In the {@code PieChart} example, {@link android.view.View#onSizeChanged onSizeChanged()} is where the {@code PieChart} view calculates the bounding rectangle of the pie chart and the relative position of the text label and other visual elements.

When your view is assigned a size, the layout manager assumes that the size includes all of the view's padding. You must handle the padding values when you calculate your view's size. Here's a snippet from {@code PieChart.onSizeChanged()} that shows how to do this:

       // Account for padding
       float xpad = (float)(getPaddingLeft() + getPaddingRight());
       float ypad = (float)(getPaddingTop() + getPaddingBottom());

       // Account for the label
       if (mShowText) xpad += mTextWidth;

       float ww = (float)w - xpad;
       float hh = (float)h - ypad;

       // Figure out how big we can make the pie.
       float diameter = Math.min(ww, hh);

If you need finer control over your view's layout parameters, implement {@link android.view.View#onMeasure onMeasure()}. This method's parameters are {@link android.view.View.MeasureSpec} values that tell you how big your view's parent wants your view to be, and whether that size is a hard maximum or just a suggestion. As an optimization, these values are stored as packed integers, and you use the static methods of {@link android.view.View.MeasureSpec} to unpack the information stored in each integer.

Here's an example implementation of {@link android.view.View#onMeasure onMeasure()}. In this implementation, {@code PieChart} attempts to make its area big enough to make the pie as big as its label:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   // Try for a width based on our minimum
   int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
   int w = resolveSizeAndState(minw, widthMeasureSpec, 1);

   // Whatever the width ends up being, ask for a height that would let the pie
   // get as big as it can
   int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop();
   int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int)mTextWidth, heightMeasureSpec, 0);

   setMeasuredDimension(w, h);
}

There are three important things to note in this code:

Draw!

Once you have your object creation and measuring code defined, you can implement {@link android.view.View#onDraw(android.graphics.Canvas) onDraw()}. Every view implements {@link android.view.View#onDraw(android.graphics.Canvas) onDraw()} differently, but there are some common operations that most views share:

For example, here's the code that draws {@code PieChart}. It uses a mix of text, lines, and shapes.

protected void onDraw(Canvas canvas) {
   super.onDraw(canvas);

   // Draw the shadow
   canvas.drawOval(
           mShadowBounds,
           mShadowPaint
   );

   // Draw the label text
   canvas.drawText(mData.get(mCurrentItem).mLabel, mTextX, mTextY, mTextPaint);

   // Draw the pie slices
   for (int i = 0; i < mData.size(); ++i) {
       Item it = mData.get(i);
       mPiePaint.setShader(it.mShader);
       canvas.drawArc(mBounds,
               360 - it.mEndAngle,
               it.mEndAngle - it.mStartAngle,
               true, mPiePaint);
   }

   // Draw the pointer
   canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint);
   canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint);
}