page.title=Drawing Watch Faces @jd:body
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.
This lesson explains how the system invokes the methods in the
watch face service using examples from the WatchFace sample
included in the Android SDK. This sample is located in the
android-sdk/samples/android-21/wearable/WatchFace
directory. Many aspects of the
service implementations described here (such as initialization and detecting device features)
apply to any watch face, so you can reuse some of the code in your own watch faces.
Figure 1. The analog and digital watch faces in the WatchFace sample.
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.
To initialize your watch face, follow these steps:
Engine.onCreate()
method.Engine.onVisibilityChanged()
method.The following sections describe these steps in detail.
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 WatchFaceService.Engine
implementation.
Declare variables for the following elements:
The AnalogWatchFaceService.Engine
class in the WatchFace sample defines
these variables as shown in the snippet below. 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 invalidate()
method and the system then calls
the onDraw()
method to redraw the watch face.
private class Engine extends CanvasWatchFaceService.Engine { static final int MSG_UPDATE_TIME = 0; /* a time object */ Time mTime; /* 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() { @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() { @Override public void onReceive(Context context, Intent intent) { mTime.clear(intent.getStringExtra("time-zone")); mTime.setToNow(); } }; /* service methods (see other sections) */ ... }
After you have declared member variables for bitmap resources, paint styles, and other elements that you reuse every time your 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.
In the Engine.onCreate()
method, initialize the following elements:
The Engine.onCreate()
method in the AnalogWatchFaceService
class
initializes these elements as follows:
@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); 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 an object to hold the time */ mTime = new Time(); }
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. You later use these
styles to draw the elements of your watch face inside the Engine.onDraw()
method,
as described in Drawing Your Watch Face.
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.
Note: In ambient mode, the system does not reliably call the custom timer. To update the watch face in ambient mode, see Update the watch face in ambient mode.
An example timer definition from the AnalogWatchFaceService
class that ticks once
every second is shown in Declare variables. In the
Engine.onVisibilityChanged()
method, start the custom timer if these two
conditions apply:
The AnalogWatchFaceService
class schedules the next timer tick if required as
follows:
private void updateTimer() { mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME); if (shouldTimerBeRunning()) { mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME); } } private boolean shouldTimerBeRunning() { return isVisible() && !isInAmbientMode(); }
This custom timer ticks once every second, as described in Declare variables.
In the Engine.onVisibilityChanged()
method, start the timer if required and
and register the receiver for time zone changes as follows:
@Override public void onVisibilityChanged(boolean visible) { super.onVisibilityChanged(visible); if (visible) { registerReceiver(); // Update time zone in case it changed while we weren't visible. mTime.clear(TimeZone.getDefault().getID()); mTime.setToNow(); } 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(); }
When the watch face is visible, the onVisibilityChanged()
method registers
the receiver for time zone changes and starts the custom timer if the device is in interactive
mode. When the watch face is not visible, this method stops the custom timer and unregisters
the receiver for time zone changes. The registerReceiver()
and
unregisterReceiver()
methods are implemented as follows:
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); }
In ambient mode, the system calls the Engine.onTimeTick()
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
Initialize the custom timer.
In ambient mode, most watch face implementations simply invalidate the canvas to redraw the watch
face in the Engine.onTimeTick()
method:
@Override public void onTimeTick() { super.onTimeTick(); invalidate(); }
Watch faces should not interfere with system UI elements, as described in Accommodate System UI Elements. 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.
Android Wear enables you to configure the following aspects of the system UI when your watch face is active:
To configure these aspects of the system UI, create a WatchFaceStyle
instance
and pass it to the Engine.setWatchFaceStyle()
method.
The AnalogWatchFaceService
class configures the system UI as follows:
@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()); ... }
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).
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.
For more details about configuring the system UI, see the
API reference for the
WatchFaceStyle
class.
The system calls the Engine.onPropertiesChanged()
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.
The following code snippet shows how to obtain these properties:
@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); }
You should take these device properties into account when drawing your watch face:
For more information about low-bit ambient mode and burn-in protection, see Optimize for Special Screens.
When the device switches between ambient and interactive modes, the system calls the
Engine.onAmbientModeChanged()
method. Your service implementation should make
any necessary adjustments to switch between modes and then call the invalidate()
method for the system to redraw the watch face.
The following snippet shows how this method is implemented in the
AnalogWatchFaceService
class inside the WatchFace sample:
@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(); }
This example makes adjustments to some graphic styles and invalidates the canvas so the system can redraw the watch face.
To draw a custom watch face, the system calls the Engine.onDraw()
method with a
{@link android.graphics.Canvas} instance and the bounds in which you should draw your watch face.
The bounds account for 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:
onDraw()
method, scale your background
to fit.The AnalogWatchFaceService
class in the WatchFace sample follows these
steps to implement the onDraw()
method as follows:
@Override public void onDraw(Canvas canvas, Rect bounds) { // Update the time mTime.setToNow(); int width = bounds.width(); int height = bounds.height(); // Draw the background, scaled to fit. if (mBackgroundScaledBitmap == null || mBackgroundScaledBitmap.getWidth() != width || mBackgroundScaledBitmap.getHeight() != height) { mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap, width, height, true /* filter */); } 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 secRot = mTime.second / 30f * (float) Math.PI; int minutes = mTime.minute; float minRot = minutes / 30f * (float) Math.PI; float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.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); }
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
onCreate()
method. The second hand is only drawn in interactive mode, not in
ambient mode.
For more information about drawing on a Canvas instance, see Canvas and Drawables.
The WatchFace sample in the Android SDK includes additional watch faces that you
can refer to as examples of how to implement the onDraw()
method.