Home | History | Annotate | Download | only in watch-faces
      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         &#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
    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 &#64;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() &amp;&amp; !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 &#64;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 &#64;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 &#64;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 &#64;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 &#64;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 &#64;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