• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1page.title=Drawing Watch Faces
2
3@jd:body
4
5<div id="tb-wrapper">
6<div id="tb">
7<h2>This lesson teaches you to</h2>
8<ol>
9  <li><a href="#Initialize">Initialize Your Watch Face</a></li>
10  <li><a href="#SystemUI">Configure the System UI</a></li>
11  <li><a href="#Screen">Obtain Information About the Device Screen</a></li>
12  <li><a href="#Modes">Respond to Changes Between Modes</a></li>
13  <li><a href="#Drawing">Draw Your Watch Face</a></li>
14</ol>
15<h2>You should also read</h2>
16<ul>
17  <li><a href="{@docRoot}design/wear/watchfaces.html">Watch Faces for Android Wear</a></li>
18</ul>
19</div>
20</div>
21
22<p>After you have configured your project and added a class that implements the watch
23face service, you can start writing code to initialize and draw your custom watch face.</p>
24
25<p>This lesson explains how the system invokes the methods in the
26watch face service using examples from the <em>WatchFace</em> sample
27included in the Android SDK. This sample is located in the
28<code>android-sdk/samples/android-21/wearable/WatchFace</code> directory. Many aspects of the
29service implementations described here (such as initialization and detecting device features)
30apply to any watch face, so you can reuse some of the code in your own watch faces.</p>
31
32
33<img src="{@docRoot}training/wearables/watch-faces/images/preview_analog.png"
34     width="180" height="180" alt="" style="margin-top:12px"/>
35<img src="{@docRoot}training/wearables/watch-faces/images/preview_digital.png"
36     width="180" height="180" alt="" style="margin-left:25px;margin-top:12px"/>
37<p class="img-caption">
38<strong>Figure 1.</strong> The analog and digital watch faces in
39the <em>WatchFace</em> sample.</p>
40
41
42<h2 id="Initialize">Initialize Your Watch Face</h2>
43
44<p>When the system loads your service, you should allocate and initialize most of the resources
45that your watch face needs, including loading bitmap resources, creating timer objects to run
46custom animations, configuring paint styles, and performing other computations. You can usually
47perform these operations only once and reuse their results. This practice improves the performance
48of your watch face and makes it easier to maintain your code.</p>
49
50<p>To initialize your watch face, follow these steps:</p>
51
52<ol>
53<li>Declare variables for a custom timer, graphic objects, and other elements.</li>
54<li>Initialize the watch face elements in the <code>Engine.onCreate()</code> method.</li>
55<li>Initialize the custom timer in the <code>Engine.onVisibilityChanged()</code> method.</li>
56</ol>
57
58<p>The following sections describe these steps in detail.</p>
59
60<h3 id="Variables">Declare variables</h3>
61
62<p>The resources that you intialize when the system loads your service need to be accessible
63at different points throughout your implementation, so you can reuse them. You achieve this
64by declaring member variables for these resources in your <code>WatchFaceService.Engine</code>
65implementation.</p>
66
67<p>Declare variables for the following elements:</p>
68
69<dl>
70<dt><em>Graphic objects</em></dt>
71<dd>Most watch faces contain at least one bitmap image used as the background of the watch face,
72as described in
73<a href="{@docRoot}training/wearables/watch-faces/designing.html#ImplementationStrategy">Create an
74Implementation Strategy</a>. You can use additional bitmap images that represent clock hands or
75other design elements of your watch face.</dd>
76<dt><em>Periodic timer</em></dt>
77<dd>The system notifies the watch face once a minute when the time changes, but some watch faces
78run animations at custom time intervals. In these cases, you need to provide a custom timer that
79ticks with the frequency required to update your watch face.</dd>
80<dt><em>Time zone change receiver</em></dt>
81<dd>Users can adjust their time zone when they travel, and the system broadcasts this event.
82Your service implementation must register a broadcast receiver that is notified when the time
83zone changes and update the time accordingly.</dd>
84</dl>
85
86<p>The <code>AnalogWatchFaceService.Engine</code> class in the <em>WatchFace</em> sample defines
87these variables as shown in the snippet below. The custom timer is implemented as a
88{@link android.os.Handler} instance that sends and processes delayed messages using the thread's
89message queue. For this particular watch face, the custom timer ticks once every second. When the
90timer ticks, the handler calls the <code>invalidate()</code> method and the system then calls
91the <code>onDraw()</code> method to redraw the watch face.</p>
92
93<pre>
94private class Engine extends CanvasWatchFaceService.Engine {
95    static final int MSG_UPDATE_TIME = 0;
96
97    /* a time object */
98    Time mTime;
99
100    /* device features */
101    boolean mLowBitAmbient;
102
103    /* graphic objects */
104    Bitmap mBackgroundBitmap;
105    Bitmap mBackgroundScaledBitmap;
106    Paint mHourPaint;
107    Paint mMinutePaint;
108    ...
109
110    /* handler to update the time once a second in interactive mode */
111    final Handler mUpdateTimeHandler = new Handler() {
112        &#64;Override
113        public void handleMessage(Message message) {
114            switch (message.what) {
115                case MSG_UPDATE_TIME:
116                    invalidate();
117                    if (shouldTimerBeRunning()) {
118                        long timeMs = System.currentTimeMillis();
119                        long delayMs = INTERACTIVE_UPDATE_RATE_MS
120                                - (timeMs % INTERACTIVE_UPDATE_RATE_MS);
121                        mUpdateTimeHandler
122                            .sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
123                    }
124                    break;
125            }
126        }
127    };
128
129    /* receiver to update the time zone */
130    final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
131        &#64;Override
132        public void onReceive(Context context, Intent intent) {
133            mTime.clear(intent.getStringExtra("time-zone"));
134            mTime.setToNow();
135        }
136    };
137
138    /* service methods (see other sections) */
139    ...
140}
141</pre>
142
143<h3 id="InitializeElements">Initialize watch face elements</h3>
144
145<p>After you have declared member variables for bitmap resources, paint styles, and other
146elements that you reuse every time your redraw your watch face, initialize them when the system
147loads your service. Initializing these elements only once and reusing them improves performance
148and battery life.</p>
149
150<p>In the <code>Engine.onCreate()</code> method, initialize the following elements:</p>
151
152<ul>
153<li>Load the background image.</li>
154<li>Create styles and colors to draw graphic objects.</li>
155<li>Allocate an object to hold the time.</li>
156<li>Configure the system UI.</li>
157</ul>
158
159<p>The <code>Engine.onCreate()</code> method in the <code>AnalogWatchFaceService</code> class
160initializes these elements as follows:</p>
161
162<pre>
163&#64;Override
164public void onCreate(SurfaceHolder holder) {
165    super.onCreate(holder);
166
167    /* configure the system UI (see next section) */
168    ...
169
170    /* load the background image */
171    Resources resources = AnalogWatchFaceService.this.getResources();
172    Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
173    mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
174
175    /* create graphic styles */
176    mHourPaint = new Paint();
177    mHourPaint.setARGB(255, 200, 200, 200);
178    mHourPaint.setStrokeWidth(5.0f);
179    mHourPaint.setAntiAlias(true);
180    mHourPaint.setStrokeCap(Paint.Cap.ROUND);
181    ...
182
183    /* allocate an object to hold the time */
184    mTime = new Time();
185}
186</pre>
187
188<p>The background bitmap is loaded only once when the system initializes the watch face. The
189graphic styles are instances of the {@link android.graphics.Paint} class. You later use these
190styles to draw the elements of your watch face inside the <code>Engine.onDraw()</code> method,
191as described in <a href="#Drawing">Drawing Your Watch Face</a>.</p>
192
193<h3 id="Timer">Initialize the custom timer</h3>
194
195<p>As a watch face developer, you decide how often you want to update your watch face by
196providing a custom timer that ticks with the required frequency while the device is in
197interactive mode. This enables you to create custom animations and other visual effects.
198</p>
199
200<p class="note"><strong>Note:</strong> In ambient mode, the system does not reliably call the
201custom timer. To update the watch face in ambient mode, see <a href="#TimeTick">Update the watch
202face in ambient mode</a>.</p>
203
204<p>An example timer definition from the <code>AnalogWatchFaceService</code> class that ticks once
205every second is shown in <a href="#Variables">Declare variables</a>. In the
206<code>Engine.onVisibilityChanged()</code> method, start the custom timer if these two
207conditions apply:</p>
208
209<ul>
210<li>The watch face is visible.</li>
211<li>The device is in interactive mode.</li>
212</ul>
213
214<p>The <code>AnalogWatchFaceService</code> class schedules the next timer tick if required as
215follows:</p>
216
217<pre>
218private void updateTimer() {
219    mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
220    if (shouldTimerBeRunning()) {
221        mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
222    }
223}
224
225private boolean shouldTimerBeRunning() {
226    return isVisible() &amp;&amp; !isInAmbientMode();
227}
228</pre>
229
230<p>This custom timer ticks once every second, as described in <a href="#Variables">Declare
231variables</a>.</p>
232
233<p>In the <code>Engine.onVisibilityChanged()</code> method, start the timer if required and
234and register the receiver for time zone changes as follows:</p>
235
236<pre>
237&#64;Override
238public void onVisibilityChanged(boolean visible) {
239    super.onVisibilityChanged(visible);
240
241    if (visible) {
242        registerReceiver();
243
244        // Update time zone in case it changed while we weren't visible.
245        mTime.clear(TimeZone.getDefault().getID());
246        mTime.setToNow();
247    } else {
248        unregisterReceiver();
249    }
250
251    // Whether the timer should be running depends on whether we're visible and
252    // whether we're in ambient mode), so we may need to start or stop the timer
253    updateTimer();
254}
255</pre>
256
257<p>When the watch face is visible, the <code>onVisibilityChanged()</code> method registers
258the receiver for time zone changes and starts the custom timer if the device is in interactive
259mode. When the watch face is not visible, this method stops the custom timer and unregisters
260the receiver for time zone changes. The <code>registerReceiver()</code> and
261<code>unregisterReceiver()</code> methods are implemented as follows:</p>
262
263<pre>
264private void registerReceiver() {
265    if (mRegisteredTimeZoneReceiver) {
266        return;
267    }
268    mRegisteredTimeZoneReceiver = true;
269    IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
270    AnalogWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
271}
272
273private void unregisterReceiver() {
274    if (!mRegisteredTimeZoneReceiver) {
275        return;
276    }
277    mRegisteredTimeZoneReceiver = false;
278    AnalogWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
279}
280</pre>
281
282
283
284<h3 id="TimeTick">Update the watch face in ambient mode</h3>
285
286<p>In ambient mode, the system calls the <code>Engine.onTimeTick()</code> method every minute.
287It is usually sufficient to update your watch face once per minute in this mode. To update your
288watch face while in interactive mode, you must provide a custom timer as described in
289<a href="#Timer">Initialize the custom timer</a>.</p>
290
291<p>In ambient mode, most watch face implementations simply invalidate the canvas to redraw the watch
292face in the <code>Engine.onTimeTick()</code> method:</p>
293
294<pre>
295&#64;Override
296public void onTimeTick() {
297    super.onTimeTick();
298
299    invalidate();
300}
301</pre>
302
303
304
305<h2 id="SystemUI">Configure the System UI</h2>
306
307<p>Watch faces should not interfere with system UI elements, as described in
308<a href="{@docRoot}design/wear/watchfaces.html#SystemUI">Accommodate System UI Elements</a>.
309If your watch face has a light background or shows information near the bottom of the screen,
310you may have to configure the size of notification cards or enable background protection.</p>
311
312<p>Android Wear enables you to configure the following aspects of the system UI when your watch
313face is active:</p>
314
315<ul>
316<li>Specify how far the first notification card peeks into the screen.</li>
317<li>Specify whether the system draws the time over your watch face.</li>
318<li>Show or hide cards when in ambient mode.</li>
319<li>Protect the system indicators with a solid background around them.</li>
320<li>Specify the positioning of the system indicators.</li>
321</ul>
322
323<p>To configure these aspects of the system UI, create a <code>WatchFaceStyle</code> instance
324and pass it to the <code>Engine.setWatchFaceStyle()</code> method.</p>
325
326<p>The <code>AnalogWatchFaceService</code> class configures the system UI as follows:</p>
327
328<pre>
329&#64;Override
330public void onCreate(SurfaceHolder holder) {
331    super.onCreate(holder);
332
333    /* configure the system UI */
334    setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)
335            .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
336            .setBackgroundVisibility(WatchFaceStyle
337                                    .BACKGROUND_VISIBILITY_INTERRUPTIVE)
338            .setShowSystemUiTime(false)
339            .build());
340    ...
341}
342</pre>
343
344<p>The code snippet above configures peeking cards to be a single line tall, the background
345of a peeking card to show only briefly and only for interruptive notifications, and the system
346time not to be shown (since this watch face draws its own time representation).</p>
347
348<p>You can configure the style of the system UI at any point in your watch face implementation.
349For example, if the user selects a white background, you can add background protection for the
350system indicators.</p>
351
352<p>For more details about configuring the system UI, see the
353<a href="{@docRoot}shareables/training/wearable-support-docs.zip">API reference</a> for the
354<code>WatchFaceStyle</code> class.</p>
355
356
357
358<h2 id="Screen">Obtain Information About the Device Screen</h2>
359
360<p>The system calls the <code>Engine.onPropertiesChanged()</code> method when it determines
361the properties of the device screen, such as whether the device uses low-bit ambient mode and
362whether the screen requires burn-in protection.</p>
363
364<p>The following code snippet shows how to obtain these properties:</p>
365
366<pre>
367&#64;Override
368public void onPropertiesChanged(Bundle properties) {
369    super.onPropertiesChanged(properties);
370    mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
371    mBurnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION,
372            false);
373}
374</pre>
375
376<p>You should take these device properties into account when drawing your watch face:</p>
377
378<ul>
379<li>For devices that use low-bit ambient mode, the screen supports fewer bits for each color
380in ambient mode, so you should disable anti-aliasing.</li>
381<li>For devices that require burn-in protection, avoid using large blocks of white pixels in
382ambient mode and do not place content within 10 pixels of the edge of the screen, since the
383system shifts the content periodically to avoid pixel burn-in.</li>
384</ul>
385
386<p>For more information about low-bit ambient mode and burn-in protection, see
387<a href="{@docRoot}design/wear/watchfaces.html#SpecialScreens">Optimize for Special
388Screens</a>.</p>
389
390
391<h2 id="Modes">Respond to Changes Between Modes</h2>
392
393<p>When the device switches between ambient and interactive modes, the system calls the
394<code>Engine.onAmbientModeChanged()</code> method. Your service implementation should make
395any necessary adjustments to switch between modes and then call the <code>invalidate()</code>
396method for the system to redraw the watch face.</p>
397
398<p>The following snippet shows how this method is implemented in the
399<code>AnalogWatchFaceService</code> class inside the <em>WatchFace</em> sample:</p>
400
401<pre>
402&#64;Override
403public void onAmbientModeChanged(boolean inAmbientMode) {
404
405    super.onAmbientModeChanged(inAmbientMode);
406
407    if (mLowBitAmbient) {
408        boolean antiAlias = !inAmbientMode;
409        mHourPaint.setAntiAlias(antiAlias);
410        mMinutePaint.setAntiAlias(antiAlias);
411        mSecondPaint.setAntiAlias(antiAlias);
412        mTickPaint.setAntiAlias(antiAlias);
413    }
414    invalidate();
415    updateTimer();
416}
417</pre>
418
419<p>This example makes adjustments to some graphic styles and invalidates the canvas so the
420system can redraw the watch face.</p>
421
422
423
424<h2 id="Drawing">Draw Your Watch Face</h2>
425
426<p>To draw a custom watch face, the system calls the <code>Engine.onDraw()</code> method with a
427{@link android.graphics.Canvas} instance and the bounds in which you should draw your watch face.
428The bounds account for any inset areas, such as the "chin" on the bottom of some round devices.
429You can use this canvas to draw your watch face directly as follows:</p>
430
431<ol>
432<li>If this is the first invocation of the <code>onDraw()</code> method, scale your background
433to fit.</li>
434<li>Check whether the device is in ambient mode or interactive mode.</li>
435<li>Perform any required graphic computations.</li>
436<li>Draw your background bitmap on the canvas.</li>
437<li>Use the methods in the {@link android.graphics.Canvas} class to draw your watch face.</li>
438</ol>
439
440<p>The <code>AnalogWatchFaceService</code> class in the <em>WatchFace</em> sample follows these
441steps to implement the <code>onDraw()</code> method as follows:</p>
442
443<pre>
444&#64;Override
445public void onDraw(Canvas canvas, Rect bounds) {
446    // Update the time
447    mTime.setToNow();
448
449    int width = bounds.width();
450    int height = bounds.height();
451
452    // Draw the background, scaled to fit.
453    if (mBackgroundScaledBitmap == null
454        || mBackgroundScaledBitmap.getWidth() != width
455        || mBackgroundScaledBitmap.getHeight() != height) {
456        mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap,
457                                         width, height, true /* filter */);
458    }
459    canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null);
460
461    // Find the center. Ignore the window insets so that, on round watches
462    // with a "chin", the watch face is centered on the entire screen, not
463    // just the usable portion.
464    float centerX = width / 2f;
465    float centerY = height / 2f;
466
467    // Compute rotations and lengths for the clock hands.
468    float secRot = mTime.second / 30f * (float) Math.PI;
469    int minutes = mTime.minute;
470    float minRot = minutes / 30f * (float) Math.PI;
471    float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;
472
473    float secLength = centerX - 20;
474    float minLength = centerX - 40;
475    float hrLength = centerX - 80;
476
477    // Only draw the second hand in interactive mode.
478    if (!isInAmbientMode()) {
479        float secX = (float) Math.sin(secRot) * secLength;
480        float secY = (float) -Math.cos(secRot) * secLength;
481        canvas.drawLine(centerX, centerY, centerX + secX, centerY +
482                        secY, mSecondPaint);
483    }
484
485    // Draw the minute and hour hands.
486    float minX = (float) Math.sin(minRot) * minLength;
487    float minY = (float) -Math.cos(minRot) * minLength;
488    canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY,
489                    mMinutePaint);
490    float hrX = (float) Math.sin(hrRot) * hrLength;
491    float hrY = (float) -Math.cos(hrRot) * hrLength;
492    canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY,
493                    mHourPaint);
494}
495</pre>
496
497<p>This method computes the required positions for the clock hands based on the current time
498and draws them on top of the background bitmap using the graphic styles initialized in the
499<code>onCreate()</code> method. The second hand is only drawn in interactive mode, not in
500ambient mode.</p>
501
502<p>For more information about drawing on a Canvas instance, see <a
503href="{@docRoot}guide/topics/graphics/2d-graphics.html">Canvas and Drawables</a>.</p>
504
505<p>The <em>WatchFace</em> sample in the Android SDK includes additional watch faces that you
506can refer to as examples of how to implement the <code>onDraw()</code> method.</p>
507