This is part 2 of a 4 part series where we explore the challenges of making wearable apps, by building an Android wear watch face with real-time weather data. In Part 1 We provided an overview of the app and created our first watch face. In this part we take a deeper dive into the watch face and the considerations that must be made in making the watch face. We will follow up in the Part 3 about building the real-time weather communication api and finally in Part 4, we show how to package and publish a watch face app.
You can checkout the source code on Github or download the final watch face from the Google Play Store.
This tutorial is separated to four posts:
- 1 – Overview & Drawing a watch face
- 2 – The watch interface (Current)
- 3 – Real-time weather communication
- 4 – Packaging and publishing the watch face
Square Screen and Round Screen
Android Wear supports two different screen shapes, square (280px * 280px) and round (320px * 320px). In order to build a face that works well for both styles we’ll have to account for these differences.Some areas to consider are:
- Font and Image Sizes: The round screen is larger, a font or image size that is suitable for this ends up taking too much room on a square face. Our solution was to apply two sets of sizes for fonts and images, one for each style.
- Layout: The round screen doesn’t have corners, so you’ll have to design two layouts for them or one layout suitable for both.
Additional details can be found in the Android Design Guidelines.
Style Settings
There are system elements that you can set within your watch face, for example:
- Indicators: the charging icon, disconnected icon, mute icon, etc.
- The “OK Google” hot word.
- The peek card.
- The default clock.
Figure 1: the elements on a square screen.
You can control the positioning of these elements so that they better blend into your designs. These configurable options include:
- setCardPeekMode: Change the height of the peek card
PEEK_MODE_SHORT
PEEK_MODE_VARIABLE
- setBackgroundVisibility: whether a notification’s background is always displayed or shown temporarily. The background will obscure your watch face.
- Options: BACKGROUND_VISIBILITY_PERSISTENT, BACKGROUND_VISIBILITY_INTERRUPTIVE
- setShowSystemUiTime: Whether or not to show the default clock.
- setAmbientPeekMode: Whether the peek card is visible in ambient mode or not.
- Options: AMBIENT_PEEK_MODE_HIDDEN, AMBIENT_PEEK_MODE_VISIBLE
- setPeekOpacityMode: The opacity of the peek card.
- Options: PEEK_OPACITY_MODE_OPAQUE, PEEK_OPACITY_MODE_TRANSLUCENT
- setStatusBarGravity: The position of indicators.
-
- Options: Gravity.TOP, Gravity.CENTER, Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT
-
- You combine a vertical position and a horiztonal position like so: .setStatusBarGravity(Gravity.TOP | Gravity.LEFT)
Note: the bottom positions are above the peek card and corner positions are not allowed on round screens.
- setHotwordIndicatorGravity : the position of “OK Google”.Positioning for the Hotword is set the same way as the Status Bar.
- setShowUnreadCountIndicator: Whether or not to show the unread count indicator
The round screens are less flexible when it comes to positioning of UI Elements. For example, because of the lack of corners, setStatusBarGravity(Gravity.RIGHT|Gravity.TOP) is not allowed on round Android Wear devices.In our watch face we use short peek cards, hide the peek card when in ambient mode, the peek card background is shown briefly, and we don’t show the default clock. Other watch faces may use different options.
@Overridepublic void onCreate(SurfaceHolder holder) { super.onCreate(holder); setWatchFaceStyle(new WatchFaceStyle.Builder(WeatherWatchFaceService.this) .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT) .setAmbientPeekMode(WatchFaceStyle.AMBIENT_PEEK_MODE_HIDDEN) .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE) .setShowSystemUiTime(false) .build());}
Determine screen shape
We can determine the screen’s shape using ApplyWindowInsets, and change some of our face’s styles within this event. For example, we can make the font size bigger for the round face.
boolean mIsRound;@Overridepublic void onApplyWindowInsets(WindowInsets insets) { super.onApplyWindowInsets(insets); mIsRound = insets.isRound(); if (mIsRound){ mTextPaint.setTextSize(30); } else{ mTextPaint.setTextSize(25); }}
Interactive Mode and Ambient Mode
Unlike Android phones and tablets which just have two screen states, on or off, Wear devices have a third screen mode called ambient mode.The following are the definitions of the two active modes from the Android Developer Site
Interactive modeWhen the user moves their wrist to glance at their watch, the screen goes into interactive mode. Your design can use full color with fluid animation in this mode.Ambient modeAmbient mode helps the device conserve power. Your design should make clear to the user that the screen is in ambient mode by using only grayscale colors. Do not use a lot of white in ambient mode, since this distracting and hurts battery life on some screens. In this mode, the screen is only updated once every minute. Only show hours and minutes in ambient mode; do not show seconds. Your watch face is notified when the device switches to ambient mode, and you should thoughtfully design for it.
According to Google’s specifications, we need to change the watch face’s color palette to grayscale when in ambient mode. We can detect changes in screen mode with onAmbientModeChanged.
Step 1: Change paints in onAmbientModeChanged method
@Overridepublic void onAmbientModeChanged(boolean inAmbientMode) { super.onAmbientModeChanged(inAmbientMode); // when Ambient Mode changes, we changes the color of the background paint. if (inAmbientMode){ mBackgroundPaint.setColor(Color.BLACK); mWeatherConditionDrawable = mGrayWeatherConditionDrawable; } else { mBackgroundPaint.setColor(mBackgroundColor); mWeatherConditionDrawable = mColorWeatherConditionDrawable; } //Call invalidate() to cause the onDraw is invoked. invalidate();}
Important Note: Wear will not redraw unless we call invalidate()
TIP:Inside onDraw() you can use isInAmbientMode() to determine which state the screen is in. However, it is more efficient to use onAmbientModeChanged, as you will not be checking the screen mode on every draw call.
Step 2: Draw the background
@Overridepublic void onDraw(Canvas canvas, Rect bounds) { // Draw the background. canvas.drawRect(bounds, mBackgroundPaint); // Use canvas object to draw text on the center of the screen. canvas.drawText(time, bounds.centerX() - mTextXOffset, bounds.centerY() - mTextYOffset, mTextPaint);}
Step 3: Test it.In the Android Wear Emulator you can press Command + F7 (OSX) or F7 (Windows) to toggle between screen modes.
Setting Redraw Frequency
If you want your watch face to update more frequently than once a minute you have to create timers to redraw the screen yourself. For our watch we want to redraw the screen twice every second, ie. 500 milliseconds. In Android, there are a few classes we could use for our timer. We will use one of Google’s recommendations: Handler, which is a recursive timer. For more information on it, see this document.
final Handler mUpdateTimeHandler = new Handler() { @Override public void handleMessage(Message message) { // when the handler is active, call invalidate() to make the screen redraw. invalidate(); // only visible and interaction mode, we active the handler // AmbientMode use another time. if (isVisible() && !isInAmbientMode()) { // run again in next 500 milliseconds // the first parameter is message code, we don't use it here, so put 0. mUpdateTimeHandler.sendEmptyMessageDelayed(0, 500); } }};
Step 1: Send a message to the handler when the screen is in interactive mode and remove all the messages from the handler when the screen is turned off.
@Overridepublic void onVisibilityChanged(boolean visible) { super.onVisibilityChanged(visible); // remove the messages which are already in the queue. mUpdateTimeHandler.removeMessages(0); if (isVisible() && !isInAmbientMode()) { // send the instant message into the queue. mUpdateTimeHandler.sendEmptyMessage(0); } }
2: Remove all the messages from the handler when the screen changes to ambient mode.
@Overridepublic void onAmbientModeChanged(boolean inAmbientMode) { super.onAmbientModeChanged(inAmbientMode); invalidate(); // remove the messages which are already in the queue. mUpdateTimeHandler.removeMessages(0);}
3: Use the default timer for ambient mode. Once a minute is fast enough.
@Overridepublic void onTimeTick() { super.onTimeTick(); // the event is only invoked once a minute when the wear is in ambient mode invalidate();}
4: In onDraw, render all the images, designs, and text to the screen.
@Overridepublic void onDraw(Canvas canvas, Rect bounds) { mTime.setToNow(); boolean mShouldDrawColon = (System.currentTimeMillis() % 1000) < 500; String hourString = String.format("%02d", convertTo12Hour(mTime.hour)); String minString = String.format("%02d", mTime.minute); float hourWidth = mTimePoint.measureText(hourString); float minWidth = mTimePoint.measureText(minString); float timeXOffset = (hourWidth + mColonWidth + minWidth) / 2; float x = bounds.centerX() - timeXOffset; float y = bounds.centerY() - mTimeYOffset; canvas.drawText(hourString, x, y, mTimePoint); x += hourWidth; // draw colon when in ambient mode or the mShouldDrawColon is true. if (isInAmbientMode() || mShouldDrawColon) { canvas.drawText(":", x, y, mTimePoint); } x += mColonWidth; canvas.drawText(minString, x, y, mTimePoint);}
Logging
Running the debugger is very slow, so we recommend you use the android.unit.Log class to help you debug.For example,
@Overridepublic void onDraw(Canvas canvas, Rect bounds) { Log.d("HelloWatchFaceService", "I am drawing.");}
You can then use logcat to see what you have logged in Android Studio(Figure 20) Now you know all about the Android Wear Interface! In our next post we will show you how to connect your watch face to an Android Device and get real time weather data from the Open Weather API.
Check out Part 1 of the Tutorial here, or continue on to Part 3 here.