1 page.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 23 face 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 26 watch face service using examples from the <em>WatchFace</em> sample 27 included 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 29 service implementations described here (such as initialization and detecting device features) 30 apply 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 39 the <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 45 that your watch face needs, including loading bitmap resources, creating timer objects to run 46 custom animations, configuring paint styles, and performing other computations. You can usually 47 perform these operations only once and reuse their results. This practice improves the performance 48 of 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 63 at different points throughout your implementation, so you can reuse them. You achieve this 64 by declaring member variables for these resources in your <code>WatchFaceService.Engine</code> 65 implementation.</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, 72 as described in 73 <a href="{@docRoot}training/wearables/watch-faces/designing.html#ImplementationStrategy">Create an 74 Implementation Strategy</a>. You can use additional bitmap images that represent clock hands or 75 other 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 78 run animations at custom time intervals. In these cases, you need to provide a custom timer that 79 ticks 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. 82 Your service implementation must register a broadcast receiver that is notified when the time 83 zone 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 87 these 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 89 message queue. For this particular watch face, the custom timer ticks once every second. When the 90 timer ticks, the handler calls the <code>invalidate()</code> method and the system then calls 91 the <code>onDraw()</code> method to redraw the watch face.</p> 92 93 <pre> 94 private 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 @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 @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 146 elements that you reuse every time your redraw your watch face, initialize them when the system 147 loads your service. Initializing these elements only once and reusing them improves performance 148 and 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 160 initializes these elements as follows:</p> 161 162 <pre> 163 @Override 164 public 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 189 graphic styles are instances of the {@link android.graphics.Paint} class. You later use these 190 styles to draw the elements of your watch face inside the <code>Engine.onDraw()</code> method, 191 as 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 196 providing a custom timer that ticks with the required frequency while the device is in 197 interactive 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 201 custom timer. To update the watch face in ambient mode, see <a href="#TimeTick">Update the watch 202 face in ambient mode</a>.</p> 203 204 <p>An example timer definition from the <code>AnalogWatchFaceService</code> class that ticks once 205 every 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 207 conditions 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 215 follows:</p> 216 217 <pre> 218 private void updateTimer() { 219 mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME); 220 if (shouldTimerBeRunning()) { 221 mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME); 222 } 223 } 224 225 private boolean shouldTimerBeRunning() { 226 return isVisible() && !isInAmbientMode(); 227 } 228 </pre> 229 230 <p>This custom timer ticks once every second, as described in <a href="#Variables">Declare 231 variables</a>.</p> 232 233 <p>In the <code>Engine.onVisibilityChanged()</code> method, start the timer if required and 234 and register the receiver for time zone changes as follows:</p> 235 236 <pre> 237 @Override 238 public 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 258 the receiver for time zone changes and starts the custom timer if the device is in interactive 259 mode. When the watch face is not visible, this method stops the custom timer and unregisters 260 the receiver for time zone changes. The <code>registerReceiver()</code> and 261 <code>unregisterReceiver()</code> methods are implemented as follows:</p> 262 263 <pre> 264 private 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 273 private 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. 287 It is usually sufficient to update your watch face once per minute in this mode. To update your 288 watch 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 292 face in the <code>Engine.onTimeTick()</code> method:</p> 293 294 <pre> 295 @Override 296 public 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>. 309 If your watch face has a light background or shows information near the bottom of the screen, 310 you 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 313 face 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 324 and 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 @Override 330 public 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 345 of a peeking card to show only briefly and only for interruptive notifications, and the system 346 time 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. 349 For example, if the user selects a white background, you can add background protection for the 350 system 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 361 the properties of the device screen, such as whether the device uses low-bit ambient mode and 362 whether 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 @Override 368 public 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 380 in 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 382 ambient mode and do not place content within 10 pixels of the edge of the screen, since the 383 system 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 388 Screens</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 395 any necessary adjustments to switch between modes and then call the <code>invalidate()</code> 396 method 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 @Override 403 public 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 420 system 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. 428 The bounds account for any inset areas, such as the "chin" on the bottom of some round devices. 429 You 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 433 to 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 441 steps to implement the <code>onDraw()</code> method as follows:</p> 442 443 <pre> 444 @Override 445 public 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 498 and 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 500 ambient mode.</p> 501 502 <p>For more information about drawing on a Canvas instance, see <a 503 href="{@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 506 can refer to as examples of how to implement the <code>onDraw()</code> method.</p> 507