Android Wear / WatchFace

Something O’Clock – Part 2

On 1st April 2016 I published Something O’Clock, a watch face app for Android Wear, to Play Store. The app is lighthearted in nature (because of the date of publication), it allows the user to set the time to “beer o’clock”, or “sleep o’clock”, or even “burger o’clock”. Although the app itself is quite lighthearted the code behind it is worthy of study and, in this series we’ll take a look at various aspects of developing custom watch faces for Android Wear. In this article we’ll look at the layout and drawing of the watch face.

round_wear_screenshotThe image shows that the watch face consists of two items of test: the “something” (which can be one of a number of different words), and the “o’clock” (which remains static). These are drawn one above the other, and both are sized to match the width of the displayable area. So before we can actually begin positioning them we need to determine this area, and this is where InsetCalculator comes in – its function is to determine the insets:

So we have some basic checks to determine whether the bounds have changed since we last calculated the insets – let’s save some processor cycles if nothing has changed. When we have a square display things are pretty easy – we just have a fixed inset from the edge which we obtain from a dimen value.

It’s a little more complex for circular displays – consider the following diagram:

inset calculations

The dotted square is the largest possible square which can fit within the circle of the display, and this is what we need to calculate. The inset that we’re interested in is the length of the dotted red line labelled ‘i’. To calculate this we first need to find the length of the solid red line which runs from the edge of the square to the centre of the circle. Pythagoras can help us here. We know the diameter of the circle (it’s the width of the circle bounds), and the radius is half of this. If we draw the blue line ‘R’ (which is the radius) at 45° to the horizontal we get a triangle shown. The dotted green and solid red sides are the same length, and we can determine these by squaring the radius, dividing it by two, and finding the square root of that. If we now subtract this from the radius we get the inset value ‘i’:

i = R – √(R²/2)

That’s precisely what we’re doing in calculateRoundInset(). By applying this inset to each side of the bounding rectangle we get the area denoted by the dotted square.

Now that we know the insets we need to position the two pieces of text within it and TextLayout is responsible for this:

This class is responsible for positioning and drawing the text elements. We initialise it with a lit of the individual lines of text – as it stands this will always be “something” and “o’clock” but well expand on this later in the series. Ultimately each these will be represented by a Text object which actually draws the text on the Canvas – you can see this in draw(). The key responsibility of TextLayout is to create, position and size those Text objects. Once again we have some caching here to prevent us from re-calculating each time we draw – only when the layout has actually changed either because the bounds have changed (extremely unlikely, but it’s better to plan for all eventualities), and if the text itself changes (which it will later on).

When we calculate the layout we first create a TextPositioner which will perform the calculations for each of the lines and return a correctly sized and positioned Text object which gets stored and can be drawn repeatedly until the layout changes again. This is all done in the build() method. We’ll take a look at how TextPositioner works internally in a moment, but for now all we need to know is that the creation of these performs some initial measuring and initial sizing of text to match the width of the bounding rectangle.

In the layout() method we perform a couple of further passes. The first will position the text elements based upon the text sizes that have already been calculated (the line height retrieved from the positioner is the height of its line – albeit not necessarily the final size). This enables us to position the text elements relative to each other.

The second pass is then to adjust things. If we have exceeded the height of the bounding rectangle, then we need to scale every thing to fit and this is done in adjustHeight(). If we haven’t exceeded the bounding rectangle then we just need to centre everything vertically and centreVertical() does this.

Let’s now take a look at to see how we calculate the sizes:

TextPositioner

When we first construct the TextPositioner we create a Paint object (which will eventually render the text) and give it a default text size. We then call adjustTextSize() which determines how wide the text will be rendered using this Paint object. By knowing the actual size and the required size we can calculate a scaling factor. We then multiply the current text size by this scale factor to determine the text size that we need to draw the text at to get text of the desired width. This then gets set on the Paint object.

Most of the rest of the class is simply getters to return various metrics of the text which TextLayout uses to position everything. The exceptions to this are adjustSize() which is used when the height exceeds to bounding rectangle and TextLayout needs to scale things down down to fit; and adjustVerticalPosition() which is used when we haven’t exceeded the bounds and TextLayout needs to vertically centre things.

Also there is createText() which returns a correctly sized and positioned Text object once all of these calculations have been completed. All that remains is to look at Text itself:

That’s pretty simple. It’s effectively an immutable object which draws the text at a given position using the Paint object created by TextPositioner. The only adjustment we can make is to change the anti-alias setting as discussed in the previous article.

Phew! There’s quite a lot there, but we finally have a working watch face:

round_wear_screenshot

So it renders, but it’s totally static. In the next article we’ll look at how we can add a companion app to enable us to change the “something”.

The source code for this article is available here.

Many thanks to Daniele Bonaldo, Sebastiano Poggi, Erik Hellman, Hasan Hosgel, Said Tahsin Dane & Murat Yener – my beta testers.

Get it on Google Play

© 2016, Mark Allison. All rights reserved.

CC BY-NC-SA 4.0 Something O’Clock – Part 2 by Styling Android is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. Permissions beyond the scope of this license may be available at http://blog.stylingandroid.com/license-information.

Leave a Reply

Your email address will not be published. Required fields are marked *