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 @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 @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@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() && !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@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@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@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@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@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@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@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