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 @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 < 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