1page.title=Custom Drawing 2parent.title=Creating Custom Views 3parent.link=index.html 4 5trainingnavtop=true 6previous.title=Creating a View Class 7previous.link=create-view.html 8next.title=Making the View Interactive 9next.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" 32class="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 39according to your 40application'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 45android.view.View#onDraw(android.graphics.Canvas) onDraw()} method. The parameter to {@link 46android.view.View#onDraw(android.graphics.Canvas) onDraw()} is a {@link 47android.graphics.Canvas Canvas} object that the view can use to draw itself. The {@link 48android.graphics.Canvas Canvas} 49class defines methods for drawing text, lines, bitmaps, and many other graphics primitives. You can 50use these methods in 51{@link 52android.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 55android.graphics.Paint Paint} 56object. 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 69android.graphics.Paint Paint} provides methods to define that line's color. {@link 70android.graphics.Canvas Canvas} has a 71method to draw a rectangle, while {@link android.graphics.Paint Paint} defines whether to fill that 72rectangle with a 73color or leave it empty. Simply put, {@link android.graphics.Canvas Canvas} defines shapes that you 74can draw on the 75screen, while {@link android.graphics.Paint Paint} defines the color, style, font, and so forth of 76each shape you 77draw.</p> 78 79<p>So, before you draw anything, you need to create one or more {@link android.graphics.Paint Paint} 80objects. The {@code PieChart} example does this in a method called {@code init}, which is 81called from the 82constructor:</p> 83 84<pre> 85private 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, 107and many drawing 108objects require expensive initialization. Creating drawing objects within your {@link 109android.view.View#onDraw(android.graphics.Canvas) onDraw()} 110method significantly 111reduces 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 116views often need to 117perform multiple layout calculations depending on the size and shape of their area on screen. You 118should never make 119assumptions about the size of your view on the screen. Even if only one app uses your view, that app 120needs to handle 121different screen sizes, multiple screen densities, and various aspect ratios in both portrait and 122landscape mode.</p> 123 124<p>Although {@link android.view.View} has many methods for handling measurement, most of them do not 125need to be 126overridden. If your view doesn't need special control over its size, you only need to override one 127method: {@link 128android.view.View#onSizeChanged onSizeChanged()}.</p> 129 130<p>{@link 131android.view.View#onSizeChanged onSizeChanged()} is called when your view is first assigned a size, 132and again if the size of your view changes 133for any reason. Calculate positions, dimensions, and any other values related to your view's size in 134{@link 135android.view.View#onSizeChanged onSizeChanged()}, instead of recalculating them every time you draw. 136In the {@code PieChart} example, {@link 137android.view.View#onSizeChanged onSizeChanged()} is 138where the {@code PieChart} view calculates the bounding rectangle of the pie chart and the relative position 139of the text label 140and 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 143view's padding. You 144must handle the padding values when you calculate your view's size. Here's a snippet from {@code 145PieChart.onSizeChanged()} 146that 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 164android.view.View#onMeasure onMeasure()}. This method's parameters are 165{@link android.view.View.MeasureSpec} values that tell you how big your view's 166parent wants your view to be, and whether that size is a hard maximum or just a suggestion. As an 167optimization, these 168values are stored as packed integers, and you use the static methods of 169{@link android.view.View.MeasureSpec} to 170unpack the information 171stored 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@Override 180protected 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> 258protected 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 < 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