Home | History | Annotate | Download | only in custom-views
      1 page.title=Custom Drawing
      2 parent.title=Creating Custom Views
      3 parent.link=index.html
      4 
      5 trainingnavtop=true
      6 previous.title=Creating a View Class
      7 previous.link=create-view.html
      8 next.title=Making the View Interactive
      9 next.link=making-interactive.html
     10 
     11 @jd:body
     12 
     13 <div id="tb-wrapper">
     14     <div id="tb">
     15 
     16         <h2>This lesson teaches you to</h2>
     17         <ol>
     18             <li><a href="#ondraw">Override onDraw()</a></li>
     19             <li><a href="#createobject">Create Drawing Objects</a></li>
     20             <li><a href="#layoutevent">Handle Layout Events</a></li>
     21             <li><a href="#draw">Draw!</a></li>
     22         </ol>
     23 
     24         <h2>You should also read</h2>
     25         <ul>
     26             <li><a href="{@docRoot}guide/topics/graphics/2d-graphics.html">
     27               Canvas and Drawables</a></li>
     28         </ul>
     29 <h2>Try it out</h2>
     30 <div class="download-box">
     31 <a href="{@docRoot}shareables/training/CustomView.zip"
     32 class="button">Download the sample</a>
     33 <p class="filename">CustomView.zip</p>
     34 </div>
     35     </div>
     36 </div>
     37 
     38 <p>The most important part of a custom view is its appearance. Custom drawing can be easy or complex
     39 according to your
     40 application's needs. This lesson covers some of the most common operations.</p>
     41 
     42 <h2 id="overrideondraw">Override onDraw()</h2>
     43 
     44 <p>The most important step in drawing a custom view is to override the {@link
     45 android.view.View#onDraw(android.graphics.Canvas) onDraw()} method. The parameter to {@link
     46 android.view.View#onDraw(android.graphics.Canvas) onDraw()} is a {@link
     47 android.graphics.Canvas Canvas} object that the view can use to draw itself. The {@link
     48 android.graphics.Canvas Canvas}
     49 class defines methods for drawing text, lines, bitmaps, and many other graphics primitives. You can
     50 use these methods in
     51 {@link
     52 android.view.View#onDraw(android.graphics.Canvas) onDraw()} to create your custom user interface (UI).</p>
     53 
     54 <p>Before you can call any drawing methods, though, it's necessary to create a {@link
     55 android.graphics.Paint Paint}
     56 object. The next section discusses {@link android.graphics.Paint Paint} in more detail.</p>
     57 
     58 <h2 id="createobject">Create Drawing Objects</h2>
     59 
     60 <p>The {@link android.graphics} framework divides drawing into two areas:</p>
     61 
     62 <ul>
     63 <li><i>What</i> to draw, handled by {@link android.graphics.Canvas Canvas}</li>
     64 <li><i>How</i> to draw, handled by {@link android.graphics.Paint}.</li>
     65 </ul>
     66 
     67 <p>For instance, {@link android.graphics.Canvas Canvas} provides a method to draw a line, while
     68 {@link
     69 android.graphics.Paint Paint} provides methods to define that line's color. {@link
     70 android.graphics.Canvas Canvas} has a
     71 method to draw a rectangle, while {@link android.graphics.Paint Paint} defines whether to fill that
     72 rectangle with a
     73 color or leave it empty. Simply put, {@link android.graphics.Canvas Canvas} defines shapes that you
     74 can draw on the
     75 screen, while {@link android.graphics.Paint Paint} defines the color, style, font, and so forth of
     76 each shape you
     77 draw.</p>
     78 
     79 <p>So, before you draw anything, you need to create one or more {@link android.graphics.Paint Paint}
     80 objects. The {@code PieChart} example does this in a method called {@code init}, which is
     81 called from the
     82 constructor:</p>
     83 
     84 <pre>
     85 private void init() {
     86    mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     87    mTextPaint.setColor(mTextColor);
     88    if (mTextHeight == 0) {
     89        mTextHeight = mTextPaint.getTextSize();
     90    } else {
     91        mTextPaint.setTextSize(mTextHeight);
     92    }
     93 
     94    mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     95    mPiePaint.setStyle(Paint.Style.FILL);
     96    mPiePaint.setTextSize(mTextHeight);
     97 
     98    mShadowPaint = new Paint(0);
     99    mShadowPaint.setColor(0xff101010);
    100    mShadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL));
    101 
    102    ...
    103 </pre>
    104 
    105 
    106 <p>Creating objects ahead of time is an important optimization. Views are redrawn very frequently,
    107 and many drawing
    108 objects require expensive initialization. Creating drawing objects within your {@link
    109 android.view.View#onDraw(android.graphics.Canvas) onDraw()}
    110 method significantly
    111 reduces performance and can make your UI appear sluggish.</p>
    112 
    113 <h2 id="layouteevent">Handle Layout Events</h2>
    114 
    115 <p>In order to properly draw your custom view, you need to know what size it is. Complex custom
    116 views often need to
    117 perform multiple layout calculations depending on the size and shape of their area on screen. You
    118 should never make
    119 assumptions about the size of your view on the screen. Even if only one app uses your view, that app
    120 needs to handle
    121 different screen sizes, multiple screen densities, and various aspect ratios in both portrait and
    122 landscape mode.</p>
    123 
    124 <p>Although {@link android.view.View} has many methods for handling measurement, most of them do not
    125 need to be
    126 overridden. If your view doesn't need special control over its size, you only need to override one
    127 method: {@link
    128 android.view.View#onSizeChanged onSizeChanged()}.</p>
    129 
    130 <p>{@link
    131 android.view.View#onSizeChanged onSizeChanged()} is called when your view is first assigned a size,
    132 and again if the size of your view changes
    133 for any reason. Calculate positions, dimensions, and any other values related to your view's size in
    134 {@link
    135 android.view.View#onSizeChanged onSizeChanged()}, instead of recalculating them every time you draw.
    136 In the {@code PieChart} example, {@link
    137 android.view.View#onSizeChanged onSizeChanged()} is
    138 where the {@code PieChart} view calculates the bounding rectangle of the pie chart and the relative position
    139 of the text label
    140 and other visual elements.</p>
    141 
    142 <p>When your view is assigned a size, the layout manager assumes that the size includes all of the
    143 view's padding. You
    144 must handle the padding values when you calculate your view's size. Here's a snippet from {@code
    145 PieChart.onSizeChanged()}
    146 that shows how to do this:</p>
    147 
    148 <pre>
    149        // Account for padding
    150        float xpad = (float)(getPaddingLeft() + getPaddingRight());
    151        float ypad = (float)(getPaddingTop() + getPaddingBottom());
    152 
    153        // Account for the label
    154        if (mShowText) xpad += mTextWidth;
    155 
    156        float ww = (float)w - xpad;
    157        float hh = (float)h - ypad;
    158 
    159        // Figure out how big we can make the pie.
    160        float diameter = Math.min(ww, hh);
    161 </pre>
    162 
    163 <p>If you need finer control over your view's layout parameters, implement {@link
    164 android.view.View#onMeasure onMeasure()}. This method's parameters are
    165 {@link android.view.View.MeasureSpec} values that tell you how big your view's
    166 parent wants your view to be, and whether that size is a hard maximum or just a suggestion. As an
    167 optimization, these
    168 values are stored as packed integers, and you use the static methods of
    169 {@link android.view.View.MeasureSpec} to
    170 unpack the information
    171 stored in each integer.
    172 
    173 <p>Here's an example implementation of {@link android.view.View#onMeasure onMeasure()}.
    174     In this implementation, {@code PieChart}
    175     attempts to make its area
    176     big enough to make the pie as big as its label:</p>
    177 
    178 <pre>
    179 &#64;Override
    180 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    181    // Try for a width based on our minimum
    182    int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
    183    int w = resolveSizeAndState(minw, widthMeasureSpec, 1);
    184 
    185    // Whatever the width ends up being, ask for a height that would let the pie
    186    // get as big as it can
    187    int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop();
    188    int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int)mTextWidth, heightMeasureSpec, 0);
    189 
    190    setMeasuredDimension(w, h);
    191 }
    192 </pre>
    193 
    194 <p>There are three important things to note in this code:</p>
    195 
    196 <ul>
    197     <li>The calculations take into account the view's padding. As mentioned earlier, this is the
    198         view's
    199         responsibility.
    200     </li>
    201     <li>The helper method {@link android.view.View#resolveSizeAndState resolveSizeAndState()} is
    202         used to create the
    203         final width and height values. This helper returns an appropriate
    204         {@link android.view.View.MeasureSpec} value
    205         by comparing the view's desired size to the spec passed into
    206         {@link android.view.View#onMeasure onMeasure()}.
    207     </li>
    208     <li>{@link android.view.View#onMeasure onMeasure()} has no return value.
    209         Instead, the method communicates its results by
    210         calling {@link
    211         android.view.View#setMeasuredDimension setMeasuredDimension()}. Calling this method is
    212         mandatory. If you omit
    213         this call, the {@link android.view.View} class throws a runtime exception.
    214     </li>
    215 </ul>
    216 
    217 <h2 id="draw">Draw!</h2>
    218 
    219 <p>Once you have your object creation and measuring code defined, you can implement {@link
    220     android.view.View#onDraw(android.graphics.Canvas) onDraw()}. Every view
    221     implements {@link
    222     android.view.View#onDraw(android.graphics.Canvas) onDraw()}
    223     differently, but there are some common operations that most views
    224     share:</p>
    225 
    226 <ul>
    227     <li>Draw text using {@link android.graphics.Canvas#drawText drawText()}. Specify the typeface by
    228         calling {@link
    229         android.graphics.Paint#setTypeface setTypeface()}, and the text color by calling {@link
    230         android.graphics.Paint#setColor setColor()}.
    231     </li>
    232     <li>Draw primitive shapes using {@link android.graphics.Canvas#drawRect drawRect()}, {@link
    233         android.graphics.Canvas#drawOval drawOval()}, and {@link android.graphics.Canvas#drawArc
    234         drawArc()}. Change
    235         whether the shapes are filled, outlined, or both by calling {@link
    236         android.graphics.Paint#setStyle(android.graphics.Paint.Style) setStyle()}.
    237     </li>
    238     <li>Draw more complex shapes using the {@link android.graphics.Path} class.
    239       Define a shape by adding lines and curves to a
    240         {@link
    241         android.graphics.Path} object, then draw the shape using {@link
    242         android.graphics.Canvas#drawPath drawPath()}.
    243         Just as with primitive shapes, paths can be outlined, filled, or both, depending on the
    244         {@link android.graphics.Paint#setStyle
    245         setStyle()}.
    246     </li>
    247     <li>
    248     Define gradient fills by creating {@link android.graphics.LinearGradient} objects. Call {@link
    249     android.graphics.Paint#setShader setShader()} to use your
    250     {@link android.graphics.LinearGradient} on filled
    251     shapes.
    252     <li>Draw bitmaps using {@link android.graphics.Canvas#drawBitmap drawBitmap()}.</li>
    253 </ul>
    254 
    255 <p>For example, here's the code that draws {@code PieChart}. It uses a mix of text, lines, and shapes.</p>
    256 
    257 <pre>
    258 protected void onDraw(Canvas canvas) {
    259    super.onDraw(canvas);
    260 
    261    // Draw the shadow
    262    canvas.drawOval(
    263            mShadowBounds,
    264            mShadowPaint
    265    );
    266 
    267    // Draw the label text
    268    canvas.drawText(mData.get(mCurrentItem).mLabel, mTextX, mTextY, mTextPaint);
    269 
    270    // Draw the pie slices
    271    for (int i = 0; i &lt; mData.size(); ++i) {
    272        Item it = mData.get(i);
    273        mPiePaint.setShader(it.mShader);
    274        canvas.drawArc(mBounds,
    275                360 - it.mEndAngle,
    276                it.mEndAngle - it.mStartAngle,
    277                true, mPiePaint);
    278    }
    279 
    280    // Draw the pointer
    281    canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint);
    282    canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint);
    283 }
    284 </pre>
    285