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