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