> Drawing Watch Faces
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.
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.
>To initialize your watch face, follow these steps:
Engine.onCreate()
method.Engine.onVisibilityChanged()
method.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() { @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) { mCalendar.setTimeZone(TimeZone.getDefault()); invalidate(); } }; // service methods (see other sections) ... }
In the example above, the custom timer is implemented as a 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 theonDraw() method to redraw the watch face.
@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(); }
The background bitmap is loaded only once when the system initializes the watch face. The graphic styles are instances of the Paint
class. Use these styles to draw the elements of your watch face inside theEngine.onDraw()
method, as described in Drawing Your Watch Face.
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(); }
@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(); }> 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); }
The following snippet shows how to implement the onDraw()
method:
@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); }