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!).
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:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.examples.myfirstwatchface" >
<uses-feature android:name="android.hardware.type.watch" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.DeviceDefault" >
<activity
android:name=".MyWatchFace"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</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.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.examples.myfirstwatchface" >
<uses-feature android:name="android.hardware.type.watch" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.DeviceDefault" >
<activity
android:name=".MyWatchFace"
android:allowEmbedded="true"
android:label="@string/app_name" >
<meta-data android:name="com.google.android.clockwork.home.preview" android:resource="@drawable/ic_launcher" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.android.clockwork.home.category.HOME_BACKGROUND" />
</intent-filter>
</activity>
</application>
</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:
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:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/watch_time"
android:textSize="30sp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:textColor="#FF0000"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/watch_battery"
android:textSize="13sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF0000"
android:layout_alignRight="@id/watch_time"
android:layout_below="@+id/watch_time"
/>
</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"
(towatch_time
)android:text="76%"
(towatch_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:
public class MyWatchFace extends Activity {
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_watch_face);
final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
@Override
public void onLayoutInflated(WatchViewStub stub) {
mTextView = (TextView) stub.findViewById(R.id.text);
}
});
}
}
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
:
private final static IntentFilter INTENT_FILTER;
static {
INTENT_FILTER = new IntentFilter();
INTENT_FILTER.addAction(Intent.ACTION_TIME_TICK);
INTENT_FILTER.addAction(Intent.ACTION_TIMEZONE_CHANGED);
INTENT_FILTER.addAction(Intent.ACTION_TIME_CHANGED);
}
private final String TIME_FORMAT_DISPLAYED = "kk:mm a";
private BroadcastReceiver mTimeInfoReceiver = new BroadcastReceiver(){
@Override
public void onReceive(Context arg0, Intent intent) {
mTime.setText(
new SimpleDateFormat(TIME_FORMAT_DISPLAYED)
.format(Calendar.getInstance().getTime()));
}
};
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()
:
mTimeInfoReceiver.onReceive(MyWatchFace.this, registerReceiver(null, INTENT_FILTER)); // Here, we're just calling our onReceive() so it can set the current time.
registerReceiver(mTimeInfoReceiver, INTENT_FILTER);
And override onDestroy()
so it looks like this:
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(mTimeInfoReceiver);
}
If all we wanted to show was time, we’d be done.
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
:
private BroadcastReceiver mBatInfoReceiver = new BroadcastReceiver(){
@Override
public void onReceive(Context arg0, Intent intent) {
mBattery.setText(String.valueOf(intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) + "%"));
}
};
… and add this after you register your BroadcastReceiver
for time:
registerReceiver(mBatInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
… and remember to unregister in onDestroy()
:
unregisterReceiver(mBatInfoReceiver);
Run it!
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:
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 anActivity
, so anything that would callonPause
on an Activity on a larger device would callonPause
here. For instance, if you happen to use the wearable launcher, the seconds between selecting an app and the app starting callsonPause
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:
@Override
public void onScreenDim() {
mTime.setTextColor(Color.WHITE);
mBattery.setTextColor(Color.WHITE);
}
@Override
public void onScreenAwake() {
mTime.setTextColor(Color.RED);
mBattery.setTextColor(Color.RED);
}
If you’re using Pomepuy’s gist, your callbacks are handled in onDisplayChanged()
.
Run it! Let it dim, or force it to do so.
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