binpress

How to Create a Custom Android Wear Watch Face

There’s no official Android Wear watch face API yet, but that doesn’t mean we can’t make our own solution. There are plenty of watch faces already out there, and they are very simple to make. If you know how to make an Android application for a phone or tablet, you already know how to make a watch face.

This tutorial assumes you’ll be using the beta version of Android Studio.

Let’s get started…

The first thing you’ll want to do is create a new project. Android Studio prepares a lot of things for you. After going through these first few steps, you’ll be almost done (no, seriously!).

name your project
select mobile and wear
no mobile activity needed
blank wear activity
details for the wear

From here, you should have a mobile and a wear module in your project. For this tutorial, we won’t be showing mobile much attention, but it’s needed in order to sync the watch face to a wearable.

Let’s open up our wear‘s AndroidManifest.xml, located in wear/src/main. It should look something like this:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3.     package="com.examples.myfirstwatchface" >
  4.  
  5.     <uses-feature android:name="android.hardware.type.watch" />
  6.  
  7.     <application
  8.         android:allowBackup="true"
  9.         android:icon="@drawable/ic_launcher"
  10.         android:label="@string/app_name"
  11.         android:theme="@android:style/Theme.DeviceDefault" >
  12.         <activity
  13.             android:name=".MyWatchFace"
  14.             android:label="@string/app_name" >
  15.             <intent-filter>
  16.                 <action android:name="android.intent.action.MAIN" />
  17.  
  18.                 <category android:name="android.intent.category.LAUNCHER" />
  19.             </intent-filter>
  20.         </activity>
  21.     </application>
  22.  
  23. </manifest>

By default, our wear module is set up to be an application, launched by saying the app name or opening search on the application and scrolling down to “Start…”, then selecting our app. We don’t want this though; we want a watch face! Let’s change a few things.

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3.     package="com.examples.myfirstwatchface" >
  4.  
  5.     <uses-feature android:name="android.hardware.type.watch" />
  6.  
  7.     <application
  8.         android:allowBackup="true"
  9.         android:icon="@drawable/ic_launcher"
  10.         android:label="@string/app_name"
  11.         android:theme="@android:style/Theme.DeviceDefault" >
  12.         <activity
  13.             android:name=".MyWatchFace"
  14.             android:allowEmbedded="true"
  15.             android:label="@string/app_name" >
  16.             <meta-data android:name="com.google.android.clockwork.home.preview" android:resource="@drawable/ic_launcher" />
  17.             <intent-filter>
  18.                 <action android:name="android.intent.action.MAIN" />
  19.                 <category android:name="com.google.android.clockwork.home.category.HOME_BACKGROUND" />
  20.             </intent-filter>
  21.         </activity>
  22.     </application>
  23.  
  24. </manifest>

We didn’t change much. First, we added android:allowEmbedded="true" to the activity, and added <meta-data android:name="com.google.android.clockwork.home.preview" android:resource="@drawable/ic_launcher" /> before we added the intent-filter. This is where we set the watch face’s preview when a user is selecting a watch face. We don’t have much for that at the moment, so we can just set it as our launcher icon, which I use often when I need a placeholder image.

We also changed the intent-filter, switching our category from android.intent.category.LAUNCHER to com.google.android.clockwork.home.category.HOME_BACKGROUND.

Show me something!

Let’s actually start making a simple watch face. The end result will look something like this:

finished result

Nothing too fancy, as this tutorial is just showing you the ropes. We’re going to show the time (obviously) and display the wearable’s battery percentage, as well as handle the wearable’s ambient mode.

XML

Thanks to Android Studio, you should have three layouts pre-generated in your wear module’s res/layout folder:

  • activity_my_watch_face.xml
  • rect_activity_my_watch_face.xml
  • round_activity_my_watch_face.xml

The latter two layouts are, as the name describes, for rectangular/square and round watch faces, respectively. We want our watch face to look nice on both screen types, right? This is where the first layout comes in. It contains a single view, called a WatchViewStub, which automatically chooses the right layout based on the device’s screen type. This is done by setting the following two values of this view to point to the appropriate layout:

  • app:rectLayout="@layout/rect_activity_my_watch_face"
  • app:roundLayout="@layout/round_activity_my_watch_face"

You shouldn’t have to worry about this right now though; Android Studio has already done most of our work for us.

Both your round and square layouts should look pretty similar: a ViewGroup(LinearLayout for square, Relativelayout for round) with a single TextView.

To get started, we’re going to make these look a little more similar. Our tutorial is going for a very consistent look on both of these screen types. Future watch faces will probably want a more customized look for each of these screen types.

Create a new layout file called include_time.xml. This is a naming convention that I use for all layout files that are reusable. (Read more about reusable layouts here.) Make the contents of that file the following:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:orientation="vertical" android:layout_width="match_parent"
  4.     android:layout_height="match_parent">
  5.  
  6.     <TextView
  7.         android:id="@+id/watch_time"
  8.         android:textSize="30sp"
  9.         android:layout_centerHorizontal="true"
  10.         android:layout_centerVertical="true"
  11.         android:textColor="#FF0000"
  12.         android:layout_width="wrap_content"
  13.         android:layout_height="wrap_content" />
  14.  
  15.     <TextView
  16.         android:id="@+id/watch_battery"
  17.         android:textSize="13sp"
  18.         android:layout_width="wrap_content"
  19.         android:layout_height="wrap_content"
  20.         android:textColor="#FF0000"
  21.         android:layout_alignRight="@id/watch_time"
  22.         android:layout_below="@+id/watch_time"
  23.         />
  24. </RelativeLayout>

Those two text views are going to hold our time and battery percentage. To get a feel for what this will look like in the end, go ahead and add the following two values and look at the Preview mode available.

  • android:text="8:20pm" (to watch_time)
  • android:text="76%" (to watch_battery)

Then, in the round_ and rect_ layouts, replace the entire TextView with

<include layout="@layout/include_time"/>

That’s pretty much all the XML we need.

Java

Your MyWatchFace.java class should look something like this, thanks to pre-generated code:

  1. public class MyWatchFace extends Activity {
  2.  
  3.     private TextView mTextView;
  4.  
  5.     @Override
  6.     protected void onCreate(Bundle savedInstanceState) {
  7.         super.onCreate(savedInstanceState);
  8.         setContentView(R.layout.activity_my_watch_face);
  9.         final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
  10.         stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
  11.             @Override
  12.             public void onLayoutInflated(WatchViewStub stub) {
  13.                 mTextView = (TextView) stub.findViewById(R.id.text);
  14.             }
  15.         });
  16.     }
  17. }

We’ve already changed some things in our XML and we need to have our code reflect that. Let’s change/add a variable or two…

private TextView mTime, mBattery;

… and make sure we get proper references to them…

mTime = (TextView) stub.findViewById(R.id.watch_time);

mBattery = (TextView) stub.findViewById(R.id.watch_battery);

We’re ready to start setting the time for our watch face! To accomplish this, we’ll use a standard IntentFilter that watches — pun fully intended — for any changes to the time. We also need to format the time in a specific way. Let’s go with two digits for the hour, from 1-12, two digits for the minutes, as well as am/pm. Add the following code to your Activity:

  1. private final static IntentFilter INTENT_FILTER;
  2.     static {
  3.         INTENT_FILTER = new IntentFilter();
  4.         INTENT_FILTER.addAction(Intent.ACTION_TIME_TICK);
  5.         INTENT_FILTER.addAction(Intent.ACTION_TIMEZONE_CHANGED);
  6.         INTENT_FILTER.addAction(Intent.ACTION_TIME_CHANGED);
  7.     }
  8.  
  9. private final String TIME_FORMAT_DISPLAYED = "kk:mm a";
  10.  
  11. private BroadcastReceiver mTimeInfoReceiver = new BroadcastReceiver(){
  12.     @Override
  13.     public void onReceive(Context arg0, Intent intent) {
  14.         mTime.setText(
  15.         new SimpleDateFormat(TIME_FORMAT_DISPLAYED)
  16.         .format(Calendar.getInstance().getTime()));
  17.         }
  18.     };

That specific BroadCastReceiver is going to be used whenever an Intent that matches one in our IntentFilter gets broadcasted. When onReceive() gets called, we get an instance of the current time, format it to the time we want using a SimpleDateFormat, then display that time in our TextView.

Let’s register our BroadcastReceiver now. Add this to your onLayoutInflated():

  1. mTimeInfoReceiver.onReceive(MyWatchFace.this, registerReceiver(null, INTENT_FILTER));    //  Here, we're just calling our onReceive() so it can set the current time.
  2. registerReceiver(mTimeInfoReceiver, INTENT_FILTER);

And override onDestroy() so it looks like this:

  1. @Override
  2. protected void onDestroy() {
  3.     super.onDestroy();
  4.     unregisterReceiver(mTimeInfoReceiver);
  5. }

If all we wanted to show was time, we’d be done.

pick the watch face
just the time

Seriously, that’s all. It’s that easy to show the time.

Let’s go ahead and add battery information. Insert the following right before your previous BroadcastReceiver:

  1. private BroadcastReceiver mBatInfoReceiver = new BroadcastReceiver(){
  2.     @Override
  3.     public void onReceive(Context arg0, Intent intent) {
  4.         mBattery.setText(String.valueOf(intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) + "%"));
  5.     }
  6. };

… and add this after you register your BroadcastReceiver for time:

  1. registerReceiver(mBatInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));

… and remember to unregister in onDestroy():

  1. unregisterReceiver(mBatInfoReceiver);

Run it!

our watch face

Look at you. You’ve created your first watch face! Save this image as watchface_preview.png in your res/drawable/ folder — we’re going to use it as our preview. Go back to your wear‘s manifest and set this image as the preview:

<meta-data android:name="com.google.android.clockwork.home.preview" android:resource="@drawable/watchface_preview" />

Now, our future users know what to expect of the watch face before actually selecting it:

enter image description here

Ambient Mode

Red is a great color and all, but we don’t want super bright, colorful screens draining battery. You’ll notice that when your watch dims, the bright red stays. We want to change that, so let’s talk about Ambient Mode.

As you’ve noticed, after a few seconds of inactivity, your watch dims. The default watch faces handle this case and turn from colorful and eye-popping to very simple gray-scale versions of themselves. This is the recommended behavior, and we should try to accomplish this in our own watch face.

As the watch face API is not released yet, the community has a couple of workarounds to handle these state changes.

  • onPause/onResume:
    • Pros: Simple, nothing else needs to be added.
    • Cons: In certain cases, onPause can get called at seemingly-random times. Remember, our watch face is simply an Activity, so anything that would call onPause on an Activity on a larger device would call onPause here. For instance, if you happen to use the wearable launcher, the seconds between selecting an app and the app starting calls onPause for the watch face. Users might not understand why the watch face is dimming at this point.
  • DisplayManager:
    • Pros: Handles switching to and from ambient mode more gracefully, and at less sporadic times.
    • Cons: Takes a bit more code to actually implement.

Our example is going to use the latter approach, in one of three currently available ways.

  • Use DisplayManager directly, as described in this gist by Nicolas Pomepuy
  • Use WatchFaceActivity, a base class I made based on Pomepuy’s gist that does all of this behind the scenes, with methods you can implement for callbacks.
  • Use a registrable class made by Paul Blundell, based on WatchFaceActivity.

While this tutorial will use the WatchFaceActivity (out of bias, ssshhh), the last two implement the callbacks the exact same way, so use whichever you feel most comfortable with.

Once you’ve chosen how you want to handle your ambient states, import the neccessary code and make sure you do appropriate things in onScreenDim() and onScreenAwake(), such as:

  1. @Override
  2. public void onScreenDim() {
  3.     mTime.setTextColor(Color.WHITE);
  4.     mBattery.setTextColor(Color.WHITE);
  5. }
  6.  
  7. @Override
  8. public void onScreenAwake() {
  9.     mTime.setTextColor(Color.RED);
  10.     mBattery.setTextColor(Color.RED);
  11. }

If you’re using Pomepuy’s gist, your callbacks are handled in onDisplayChanged().

Run it! Let it dim, or force it to do so.

dimmed state

That’s all folks

Currently, this is all it takes to make a watch face. As I stated before, the public API for watch faces hasn’t been released yet, so things are bound to change. But for now, enjoy being able to customize Android Wear a bit further. For the full source code, please visit this GitHub repo.

Author: Tavon Gatling

Scroll to Top