blob: 8280237167bb0155fd0a4196522049e5f5b78f85 [file] [log] [blame]
Scott Main50e990c2012-06-21 17:14:39 -07001page.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&#64;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 &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>