blob: 30a7a6f7b62eb23fabd185e8b7add28ac5b06d2f [file] [log] [blame]
page.title=Drawing Watch Faces
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>This lesson teaches you to</h2>
<ol>
<li><a href="#Initialize">Initialize Your Watch Face</a></li>
<li><a href="#SystemUI">Configure the System UI</a></li>
<li><a href="#Screen">Obtain Information About the Device Screen</a></li>
<li><a href="#Modes">Respond to Changes Between Modes</a></li>
<li><a href="#Drawing">Draw Your Watch Face</a></li>
</ol>
<h2>You should also read</h2>
<ul>
<li><a href="{@docRoot}design/wear/watchfaces.html">Watch Faces for Android Wear</a></li>
</ul>
<h2>Related Samples</h2>
<ul>
<li><a href="{@docRoot}samples/WatchFace/index.html">WatchFace</a></li>
</ul>
</div>
</div>
<p>After you have configured your project and added a class that implements the watch
face service, you can start writing code to initialize and draw your custom watch face.</p>
<p>This lesson includes examples from the
<a href="{@docRoot}samples/WatchFace/index.html">WatchFace</a> sample to show how the system uses
the watch face service. Many aspects of the
service implementations described here (such as initialization and device features detection)
apply to any watch face, so you can reuse some of the code in your own watch faces.</p>
<img src="{@docRoot}training/wearables/watch-faces/images/preview_analog.png"
width="180" height="180" alt="" style="margin-top:12px"/>
<img src="{@docRoot}training/wearables/watch-faces/images/preview_digital.png"
width="180" height="180" alt="" style="margin-left:25px;margin-top:12px"/>
<p class="img-caption">
<strong>Figure 1.</strong> The analog and digital watch faces in
the
<a href="{@docRoot}samples/WatchFace/index.html">WatchFace</a> sample.</p>
<h2 id="Initialize">Initialize Your Watch Face</h2>
<p>When the system loads your service, you should allocate and initialize most of the resources
that your watch face needs, including loading bitmap resources, creating timer objects to run
custom animations, configuring paint styles, and performing other computations. You can usually
perform these operations only once and reuse their results. This practice improves the performance
of your watch face and makes it easier to maintain your code.</p>
<p>To initialize your watch face, follow these steps:</p>
<ol>
<li>Declare variables for a custom timer, graphic objects, and other elements.</li>
<li>Initialize the watch face elements in the
<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onCreate(android.view.SurfaceHolder)"><code>Engine.onCreate()</code></a>
method.</li>
<li>Initialize the custom timer in the
<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onVisibilityChanged(boolean)"><code>Engine.onVisibilityChanged()</code></a>
method.</li>
</ol>
<p>The following sections describe these steps in detail.</p>
<h3 id="Variables">Declare variables</h3>
<p>The resources that you intialize when the system loads your service need to be accessible
at different points throughout your implementation, so you can reuse them. You achieve this
by declaring member variables for these resources in your
<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html"><code>WatchFaceService.Engine</code></a>
implementation.</p>
<p>Declare variables for the following elements:</p>
<dl>
<dt><em>Graphic objects</em></dt>
<dd>Most watch faces contain at least one bitmap image used as the background of the watch face,
as described in
<a href="{@docRoot}training/wearables/watch-faces/designing.html#ImplementationStrategy">Create an
Implementation Strategy</a>. You can use additional bitmap images that represent clock hands or
other design elements of your watch face.</dd>
<dt><em>Periodic timer</em></dt>
<dd>The system notifies the watch face once a minute when the time changes, but some watch faces
run animations at custom time intervals. In these cases, you need to provide a custom timer that
ticks with the frequency required to update your watch face.</dd>
<dt><em>Time zone change receiver</em></dt>
<dd>Users can adjust their time zone when they travel, and the system broadcasts this event.
Your service implementation must register a broadcast receiver that is notified when the time
zone changes and update the time accordingly.</dd>
</dl>
<p>The following snippet shows how to define these variables:</p>
<pre>
private class Engine extends CanvasWatchFaceService.Engine {
static final int MSG_UPDATE_TIME = 0;
Calendar mCalendar;
// device features
boolean mLowBitAmbient;
// graphic objects
Bitmap mBackgroundBitmap;
Bitmap mBackgroundScaledBitmap;
Paint mHourPaint;
Paint mMinutePaint;
...
// handler to update the time once a second in interactive mode
final Handler mUpdateTimeHandler = new Handler() {
&#64;Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_UPDATE_TIME:
invalidate();
if (shouldTimerBeRunning()) {
long timeMs = System.currentTimeMillis();
long delayMs = INTERACTIVE_UPDATE_RATE_MS
- (timeMs % INTERACTIVE_UPDATE_RATE_MS);
mUpdateTimeHandler
.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
}
break;
}
}
};
// receiver to update the time zone
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
&#64;Override
public void onReceive(Context context, Intent intent) {
mCalendar.setTimeZone(TimeZone.getDefault());
invalidate();
}
};
// service methods (see other sections)
...
}
</pre>
<p>In the example above, the custom timer is implemented as a
{@link android.os.Handler} instance that sends and processes delayed messages using the thread's
message queue. For this particular watch face, the custom timer ticks once every second. When the
timer ticks, the handler calls the
<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#invalidate()"><code>invalidate()</code></a>
method and the system then calls the
<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#onDraw(android.graphics.Canvas, android.graphics.Rect)"<code>onDraw()</code></a>
method to redraw the watch face.</p>
<h3 id="InitializeElements">Initialize watch face elements</h3>
<p>After declaring member variables for bitmap resources, paint styles, and other
elements that you reuse every time you redraw your watch face, initialize them when the system
loads your service. Initializing these elements only once and reusing them improves performance
and battery life.</p>
<p>In the
<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onCreate(android.view.SurfaceHolder)"><code>Engine.onCreate()</code></a>
method, initialize the following elements:</p>
<ul>
<li>Load the background image.</li>
<li>Create styles and colors to draw graphic objects.</li>
<li>Allocate an object to calculate the time.</li>
<li>Configure the system UI.</li>
</ul>
<p>The following snippet shows how to initialize these elements:</p>
<pre>
&#64;Override
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
// configure the system UI (see next section)
...
// load the background image
Resources resources = AnalogWatchFaceService.this.getResources();
Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg, null);
mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
// create graphic styles
mHourPaint = new Paint();
mHourPaint.setARGB(255, 200, 200, 200);
mHourPaint.setStrokeWidth(5.0f);
mHourPaint.setAntiAlias(true);
mHourPaint.setStrokeCap(Paint.Cap.ROUND);
...
// allocate a Calendar to calculate local time using the UTC time and time zone
mCalendar = Calendar.getInstance();
}
</pre>
<p>The background bitmap is loaded only once when the system initializes the watch face. The
graphic styles are instances of the {@link android.graphics.Paint} class. Use these
styles to draw the elements of your watch face inside the
<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#onDraw(android.graphics.Canvas, android.graphics.Rect)"><code>Engine.onDraw()</code></a>
method, as described in <a href="#Drawing">Drawing Your Watch Face</a>.</p>
<h3 id="Timer">Initialize the custom timer</h3>
<p>As a watch face developer, you decide how often you want to update your watch face by
providing a custom timer that ticks with the required frequency while the device is in
interactive mode. This enables you to create custom animations and other visual effects.
</p>
<p class="note"><strong>Note:</strong> In ambient mode, the system does not reliably call the
custom timer. To update the watch face in ambient mode, see <a href="#TimeTick">Update the watch
face in ambient mode</a>.</p>
<p>An example timer definition from the <code>AnalogWatchFaceService</code> class that ticks once
every second is shown in <a href="#Variables">Declare variables</a>. In the
<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onVisibilityChanged(boolean)"><code>Engine.onVisibilityChanged()</code></a>
method, start the custom timer if these two conditions apply:</p>
<ul>
<li>The watch face is visible.</li>
<li>The device is in interactive mode.</li>
</ul>
<p>The <code>AnalogWatchFaceService</code> class schedules the next timer tick if required as
follows:</p>
<pre>
private void updateTimer() {
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
if (shouldTimerBeRunning()) {
mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
}
}
private boolean shouldTimerBeRunning() {
return isVisible() &amp;&amp; !isInAmbientMode();
}
</pre>
<p>This custom timer ticks once every second, as described in <a href="#Variables">Declare
variables</a>.</p>
<p>In the
<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onVisibilityChanged(boolean)"><code>onVisibilityChanged()</code></a>
method, start the timer if required and register the receiver for time zone changes as follows:
</p>
<pre>
&#64;Override
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
if (visible) {
registerReceiver();
// Update time zone in case it changed while we weren't visible.
mCalendar.setTimeZone(TimeZone.getDefault());
} else {
unregisterReceiver();
}
// Whether the timer should be running depends on whether we're visible and
// whether we're in ambient mode, so we may need to start or stop the timer
updateTimer();
}
</pre>
<p>When the watch face is visible, the
<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onVisibilityChanged(boolean)"><code>onVisibilityChanged()</code></a>
method registers the receiver for time zone changes. If the device is in interactive mode, this
method also starts the custom timer. When the watch face is not visible, this
method stops the custom timer and unregisters the receiver for time zone changes.
The <code>registerReceiver()</code> and <code>unregisterReceiver()</code> methods are implemented as
follows:</p>
<pre>
private void registerReceiver() {
if (mRegisteredTimeZoneReceiver) {
return;
}
mRegisteredTimeZoneReceiver = true;
IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
AnalogWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
}
private void unregisterReceiver() {
if (!mRegisteredTimeZoneReceiver) {
return;
}
mRegisteredTimeZoneReceiver = false;
AnalogWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
}
</pre>
<h3 id="TimeTick">Update the watch face in ambient mode</h3>
<p>In ambient mode, the system calls the
<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onTimeTick()"><code>Engine.onTimeTick()</code></a>
method every minute. It is usually sufficient to update your watch face once per minute in this
mode. To update your watch face while in interactive mode, you must provide a custom timer as
described in <a href="#Timer">Initialize the custom timer</a>.</p>
<p>In ambient mode, most watch face implementations simply invalidate the canvas to redraw the watch
face in the
<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onTimeTick()"<code>Engine.onTimeTick()</code></a>
method:</p>
<pre>
&#64;Override
public void onTimeTick() {
super.onTimeTick();
invalidate();
}
</pre>
<h2 id="SystemUI">Configure the System UI</h2>
<p>Watch faces should not interfere with system UI elements, as described in
<a href="{@docRoot}design/wear/watchfaces.html#SystemUI">Accommodate System UI Elements</a>.
If your watch face has a light background or shows information near the bottom of the screen,
you may have to configure the size of notification cards or enable background protection.</p>
<p>Android Wear enables you to configure the following aspects of the system UI when your watch
face is active:</p>
<ul>
<li>Specify how far the first notification card peeks into the screen.</li>
<li>Specify whether the system draws the time over your watch face.</li>
<li>Show or hide cards when in ambient mode.</li>
<li>Protect the system indicators with a solid background around them.</li>
<li>Specify the positioning of the system indicators.</li>
</ul>
<p>To configure these aspects of the system UI, create a
<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceStyle.html"><code>WatchFaceStyle</code></a>
instance and pass it to the
<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#setWatchFaceStyle(android.support.wearable.watchface.WatchFaceStyle)"><code>Engine.setWatchFaceStyle()</code></a>
method.</p>
<p>The <code>AnalogWatchFaceService</code> class configures the system UI as follows:</p>
<pre>
&#64;Override
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
// configure the system UI
setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
.setBackgroundVisibility(WatchFaceStyle
.BACKGROUND_VISIBILITY_INTERRUPTIVE)
.setShowSystemUiTime(false)
.build());
...
}
</pre>
<p>The code snippet above configures peeking cards to be a single line tall, the background
of a peeking card to show only briefly and only for interruptive notifications, and the system
time not to be shown (since this watch face draws its own time representation).</p>
<p>You can configure the style of the system UI at any point in your watch face implementation.
For example, if the user selects a white background, you can add background protection for the
system indicators.</p>
<p>For more details about configuring the system UI, see the
<a href="{@docRoot}reference/packages-wearable-support.html">Wear API reference documentation</a>.
</p>
<h2 id="Screen">Obtain Information About the Device Screen</h2>
<p>The system calls the
<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onPropertiesChanged(android.os.Bundle)"><code>Engine.onPropertiesChanged()</code></a>
method when it determines the properties of the device screen, such as whether the device uses
low-bit ambient mode and whether the screen requires burn-in protection.</p>
<p>The following code snippet shows how to obtain these properties:</p>
<pre>
&#64;Override
public void onPropertiesChanged(Bundle properties) {
super.onPropertiesChanged(properties);
mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
mBurnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION,
false);
}
</pre>
<p>You should take these device properties into account when drawing your watch face:</p>
<ul>
<li>For devices that use low-bit ambient mode, the screen supports fewer bits for each color
in ambient mode, so you should disable anti-aliasing and bitmap filtering when the device switches
to ambient mode.</li>
<li>For devices that require burn-in protection, avoid using large blocks of white pixels in
ambient mode and do not place content within 10 pixels of the edge of the screen, since the
system shifts the content periodically to avoid pixel burn-in.</li>
</ul>
<p>For more information about low-bit ambient mode and burn-in protection, see
<a href="{@docRoot}design/wear/watchfaces.html#SpecialScreens">Optimize for Special
Screens</a>. For more information on how to disable bitmap filtering, see
<a href="{@docRoot}training/wearables/watch-faces/performance.html#BitmapFiltering">Bitmap
Filtering</a>.</p>
<h2 id="Modes">Respond to Changes Between Modes</h2>
<p>When the device switches between ambient and interactive modes, the system calls the
<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onAmbientModeChanged(boolean)"><code>Engine.onAmbientModeChanged()</code></a>
method. Your service implementation should make any necessary adjustments to switch between modes
and then call the
<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#invalidate()"><code>invalidate()</code></a>
method for the system to redraw the watch face.</p>
<p>The following snippet shows how to implement this method:</p>
<pre>
&#64;Override
public void onAmbientModeChanged(boolean inAmbientMode) {
super.onAmbientModeChanged(inAmbientMode);
if (mLowBitAmbient) {
boolean antiAlias = !inAmbientMode;
mHourPaint.setAntiAlias(antiAlias);
mMinutePaint.setAntiAlias(antiAlias);
mSecondPaint.setAntiAlias(antiAlias);
mTickPaint.setAntiAlias(antiAlias);
}
invalidate();
updateTimer();
}
</pre>
<p>This example makes adjustments to some graphic styles and invalidates the canvas so the
system can redraw the watch face.</p>
<h2 id="Drawing">Draw Your Watch Face</h2>
<p>To draw a custom watch face, the system calls the
<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#onDraw(android.graphics.Canvas, android.graphics.Rect)"><code>Engine.onDraw()</code></a>
method with a {@link android.graphics.Canvas} instance and the bounds in which you should draw your
watch face. The bounds take into account any inset areas, such as the "chin" on the bottom of some
round devices. You can use this canvas to draw your watch face directly as follows:</p>
<ol>
<li>Override the
<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#onSurfaceChanged(android.view.SurfaceHolder, int, int, int)"><code>onSurfaceChanged()</code></a>
method to scale your background to fit the device any time the view changes.
<pre>
&#64;Override
public void onSurfaceChanged(
SurfaceHolder holder, int format, int width, int height) {
if (mBackgroundScaledBitmap == null
|| mBackgroundScaledBitmap.getWidth() != width
|| mBackgroundScaledBitmap.getHeight() != height) {
mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap,
width, height, true /* filter */);
}
super.onSurfaceChanged(holder, format, width, height);
}
</pre>
</li>
<li>Check whether the device is in ambient mode or interactive mode.</li>
<li>Perform any required graphic computations.</li>
<li>Draw your background bitmap on the canvas.</li>
<li>Use the methods in the {@link android.graphics.Canvas} class to draw your watch face.</li>
</ol>
<p>The following snippet shows how to implement the
<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#onDraw(android.graphics.Canvas, android.graphics.Rect)"><code>onDraw()</code></a>
method:</p>
<pre>
&#64;Override
public void onDraw(Canvas canvas, Rect bounds) {
// Update the time
mCalendar.setTimeInMillis(System.currentTimeMillis());
// Constant to help calculate clock hand rotations
final float TWO_PI = (float) Math.PI * 2f;
int width = bounds.width();
int height = bounds.height();
canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null);
// Find the center. Ignore the window insets so that, on round watches
// with a "chin", the watch face is centered on the entire screen, not
// just the usable portion.
float centerX = width / 2f;
float centerY = height / 2f;
// Compute rotations and lengths for the clock hands.
float seconds = mCalendar.get(Calendar.SECOND) +
mCalendar.get(Calendar.MILLISECOND) / 1000f;
float secRot = seconds / 60f * TWO_PI;
float minutes = mCalendar.get(Calendar.MINUTE) + seconds / 60f;
float minRot = minutes / 60f * TWO_PI;
float hours = mCalendar.get(Calendar.HOUR) + minutes / 60f;
float hrRot = hours / 12f * TWO_PI;
float secLength = centerX - 20;
float minLength = centerX - 40;
float hrLength = centerX - 80;
// Only draw the second hand in interactive mode.
if (!isInAmbientMode()) {
float secX = (float) Math.sin(secRot) * secLength;
float secY = (float) -Math.cos(secRot) * secLength;
canvas.drawLine(centerX, centerY, centerX + secX, centerY +
secY, mSecondPaint);
}
// Draw the minute and hour hands.
float minX = (float) Math.sin(minRot) * minLength;
float minY = (float) -Math.cos(minRot) * minLength;
canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY,
mMinutePaint);
float hrX = (float) Math.sin(hrRot) * hrLength;
float hrY = (float) -Math.cos(hrRot) * hrLength;
canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY,
mHourPaint);
}
</pre>
<p>This method computes the required positions for the clock hands based on the current time
and draws them on top of the background bitmap using the graphic styles initialized in the
<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onCreate(android.view.SurfaceHolder)"><code>onCreate()</code></a>
method. The second hand is only drawn in interactive mode, not in ambient mode.</p>
<p>For more information about drawing on a Canvas instance, see <a
href="{@docRoot}guide/topics/graphics/2d-graphics.html">Canvas and Drawables</a>.</p>
<p>The <a href="{@docRoot}samples/WatchFace/index.html">WatchFace</a> sample includes additional
watch faces that you can refer to as examples of how to implement the
<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#onDraw(android.graphics.Canvas, android.graphics.Rect)"><code>onDraw()</code></a>
method.</p>