Watch Face

This tutorial will guide you through the process of creating a digital watch face for your Android wear powered smartwatch. In this we will cover the basics of structuring your project and using the Wear API in order to create your first watch face.

Prerequisite

1. Better use Android Studio for development. If not installed, install it from here
2. Knowledge of Android and its fundamental concepts.
3. Watch Face API for Android Wear has published by Google so before you start
development go through the documentation and design guidelines. 


Start Creating Watch Face App

Start by creating a new project. Input your project name and package. Next, be sure to tick   both Phone and tablet and Wear platforms on the Target Android devices screen as shown   above.



Next select targeted android device.


Next, you won't need any activity created, so from the Add an activity windows (both mobile and wear), select Add no activity


Finally click on finish.You will notice that two modules were created - mobile and wear.


To implement watch face we need two component.

  1. CanvasWatchFaceService - the base class for watch faces which draw on canvas.
  2. CanvasWatchFaceService.Engine - the actual implementation of watch faces which draw on canvas.

The watch_face resource must be defined in wear/src/main/res/xml/watch_face.xml. It contain wallpaper resource. 
<?xml version="1.0" encoding="UTF-8"?>  
<wallpaper /> 

Creating Watch Face Service

As I said earlier, to create watch face service we need a base class CanvasWatchFaceService

public class WatchFaceService extends CanvasWatchFaceService { @Override public Engine onCreateEngine() { return new WatchFaceEngine() ; } private class WatchFaceEngine extends CanvasWatchFaceService.Engine { } }

CanvasWatchFaceService implements a single method - onCreateEngine() that returns your actual implementation of CanvasWatchFaceService.Engine.

It is very important to register watch face service in AndroidManifest.xml.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.example.uzer.watchfacedemoquater3">

    <uses-feature android:name="android.hardware.type.watch" />
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
    <application  android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name"  android:supportsRtl="true"  android:theme="@android:style/Theme.DeviceDefault">

        <service android:name=".WatchFaceService" android:label="@string/digital_watch_face"  android:permission="android.permission.BIND_WALLPAPER">
            <meta-data android:name="android.service.wallpaper" android:resource="@xml/watch_face" />
            <meta-data android:name="com.google.android.wearable.watchface.preview" android:resource="@drawable/preview_rectangular" />
            <meta-data android:name="com.google.android.wearable.watchface.preview_circular" android:resource="@drawable/preview_circular" />
            <meta-data android:name="com.google.android.wearable.watchface.companionConfigurationAction" android:value="com.example.android.wearable.watchface.CONFIG_DIGITAL" />
           
  <intent-filter
                <action android:name="android.service.wallpaper.WallpaperService" />
                <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
            </intent-filter>
        </service>
    </application>
</manifest>
Draw Watch face
CanvasWatchFaceService.Engine is use to draw your watch face on the canvas. This engine provide onCreate(SurfaceHolder holder). This method is used to define watch face style. Here we have used different WatchFaceStyle constants to define required behaviour.

@Override 
public void onCreate(SurfaceHolder holder) {
    super.onCreate(holder);
    setWatchFaceStyle(new WatchFaceStyle.Builder(WatchFaceService.this)
            .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
            .setAmbientPeekMode(WatchFaceStyle.AMBIENT_PEEK_MODE_HIDDEN)
            .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
            .setShowSystemUiTime(false)
            .build());
}

Managing Time

It is very important to manage time properly while we build watch face. In order to get notified every minute when we watch is not in ambient mode, we need to define handler which will take care of it.

private class WatchFaceEngine extends CanvasWatchFaceService.Engine{
    private static final long TICK_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(1); 
    private Handler timeTick;

 @Override    public void onCreate(SurfaceHolder holder) {
       super.onCreate(holder);

  timeTick = new Handler(Looper.myLooper());
  startTimerIfNecessary();
    }

 private void startTimerIfNecessary() {

      timeTick.removeCallbacks(timeRunnable);
      if (isVisible() && !isInAmbientMode()) {
        timeTick.post(timeRunnable);
      }
 }

 private final Runnable timeRunnable = new Runnable() {
  @Override      public void run() {
   onSecondTick();
   if (isVisible() && !isInAmbientMode()) {
   timeTick.postDelayed(this, TICK_PERIOD_MILLIS);
   }
  }
 };

private void onSecondTick() {
  invalidateIfNecessary();
}

private void invalidateIfNecessary() {
     if (isVisible() && !isInAmbientMode()) {
        invalidate();
     }
}

@Override public void onTimeTick() {
     super.onTimeTick();
     invalidate();
}

@Override public void onAmbientModeChanged(boolean inAmbientMode) {
      super.onAmbientModeChanged(inAmbientMode);
      watchFace.setAntiAlias(!inAmbientMode);
      watchFace.setShowSeconds(!isInAmbientMode());

      if (inAmbientMode) {
          watchFace.updateBackgroundColourToDefault();
          watchFace.updateDateAndTimeColourToDefault();
      } else {
          watchFace.restoreBackgroundColour();
          watchFace.restoreDateAndTimeColour();
      }
      invalidate();
      startTimerIfNecessary();
 }

    @Override
    public void onDestroy() {
       timeTick.removeCallbacks(timeRunnable);
       releaseGoogleApiClient();
       super.onDestroy();
    }
}

Now to draw a watch face we need to override public void onDraw(Canvas canvas, Rect bounds) method. In our case I have created a custom class  "WatchFace".


  • Paint class will hold all the information required to draw.
  • Time class is to get information of current hour, minute, second and date.
public class WatchFace {

    private static final String TIME_FORMAT_WITHOUT_SECONDS = "%02d.%02d";
    private static final String TIME_FORMAT_WITH_SECONDS = TIME_FORMAT_WITHOUT_SECONDS + ".%02d";
    private static final String DATE_FORMAT = "%02d.%02d.%d";
    private static final int DATE_AND_TIME_DEFAULT_COLOUR = Color.WHITE;
    private static final int BACKGROUND_DEFAULT_COLOUR = Color.BLACK;

    private final Paint timePaint;
    private final Paint datePaint;
    private final Paint backgroundPaint;
    private final Time time;

    private boolean shouldShowSeconds = true;
    private int backgroundColour = BACKGROUND_DEFAULT_COLOUR;
    private int dateAndTimeColour = DATE_AND_TIME_DEFAULT_COLOUR;

    public static WatchFace newInstance(Context context) {
        Paint timePaint = new Paint();
        timePaint.setColor(DATE_AND_TIME_DEFAULT_COLOUR);
        timePaint.setTextSize(context.getResources().getDimension(R.dimen.time_size));
        timePaint.setAntiAlias(true);

        Paint datePaint = new Paint();
        datePaint.setColor(DATE_AND_TIME_DEFAULT_COLOUR);
        datePaint.setTextSize(context.getResources().getDimension(R.dimen.date_size));
        datePaint.setAntiAlias(true);

        Paint backgroundPaint = new Paint();
        backgroundPaint.setColor(BACKGROUND_DEFAULT_COLOUR);

        return new WatchFace(timePaint, datePaint, backgroundPaint, new Time());
    }

    WatchFace(Paint timePaint, Paint datePaint, Paint backgroundPaint, Time time) {
        this.timePaint = timePaint;
        this.datePaint = datePaint;
        this.backgroundPaint = backgroundPaint;
        this.time = time;
    }

    public void draw(Canvas canvas, Rect bounds) {
        time.setToNow();
        canvas.drawRect(0, 0, bounds.width(), bounds.height(), backgroundPaint);

        String timeText = String.format(shouldShowSeconds ? TIME_FORMAT_WITH_SECONDS : TIME_FORMAT_WITHOUT_SECONDS, time.hour, time.minute, time.second);
        float timeXOffset = computeXOffset(timeText, timePaint, bounds);
        float timeYOffset = computeTimeYOffset(timeText, timePaint, bounds);
        canvas.drawText(timeText, timeXOffset, timeYOffset, timePaint);

        String dateText = String.format(DATE_FORMAT, time.monthDay, (time.month + 1), time.year);
        float dateXOffset = computeXOffset(dateText, datePaint, bounds);
        float dateYOffset = computeDateYOffset(dateText, datePaint);
        canvas.drawText(dateText, dateXOffset, timeYOffset + dateYOffset, datePaint);
    }

    private float computeXOffset(String text, Paint paint, Rect watchBounds) {
        float centerX = watchBounds.exactCenterX();
        float timeLength = paint.measureText(text);
        return centerX - (timeLength / 2.0f);
    }

    private float computeTimeYOffset(String timeText, Paint timePaint, Rect watchBounds) {
        float centerY = watchBounds.exactCenterY();
        Rect textBounds = new Rect();
        timePaint.getTextBounds(timeText, 0, timeText.length(), textBounds);
        int textHeight = textBounds.height();
        return centerY + (textHeight / 2.0f);
    }

    private float computeDateYOffset(String dateText, Paint datePaint) {
        Rect textBounds = new Rect();
        datePaint.getTextBounds(dateText, 0, dateText.length(), textBounds);
        return textBounds.height() + 10.0f;
    }

    public void setAntiAlias(boolean antiAlias) {
        timePaint.setAntiAlias(antiAlias);
        datePaint.setAntiAlias(antiAlias);
    }

    public void updateDateAndTimeColourTo(int colour) {
        dateAndTimeColour = colour;
        timePaint.setColor(colour);
        datePaint.setColor(colour);
    }

    public void updateTimeZoneWith(String timeZone) {
        time.clear(timeZone);
        time.setToNow();
    }

    public void setShowSeconds(boolean showSeconds) {
        shouldShowSeconds = showSeconds;
    }

    public void updateBackgroundColourTo(int colour) {
        backgroundColour = colour;
        backgroundPaint.setColor(colour);
    }

    public void restoreBackgroundColour() {
        backgroundPaint.setColor(backgroundColour);
    }

    public void updateBackgroundColourToDefault() {
        backgroundPaint.setColor(BACKGROUND_DEFAULT_COLOUR);
    }

    public void updateDateAndTimeColourToDefault() {
        timePaint.setColor(DATE_AND_TIME_DEFAULT_COLOUR);
        datePaint.setColor(DATE_AND_TIME_DEFAULT_COLOUR);
    }

    public void restoreDateAndTimeColour() {
        timePaint.setColor(dateAndTimeColour);
        datePaint.setColor(dateAndTimeColour);
    }
}





The final watch face will look like this, providing the current time and date:



Get complete code from bitbucket .

Comments

Popular posts from this blog

IONIC - Era of Hybrid Mobile Application