9.3. Watchfaces Implementation

This section highlights some implementation details of the watchfaces in this demo.

9.3.1. Overview

The demo implements two watchfaces: “Flower” and “Sport”.

../_images/watchfaces.png

They share common features:

  • Hands are vector images
  • Background is a full screen image
  • Custom animation of the hands on opening (bounce or fast rotation)
  • Custom animation of the second hands (continous or smooth tick)

They differ however on some points:

  • Flower uses a vector path with gradient to show a “radar effect”
  • Flower displays the current date and time with millisecond precision as a numeric curved text
  • Sport displays sport-related information (heart rate, steps)

9.3.2. Type hierarchy

Watchfaces are widgets. Several types were introduced to model the specificity of watchface widgets. These types are found in the com.microej.widget.watchface package of the demo application:

../_images/watchfaces_package.png

The figure below helps understanding the responsibility of each type and their relationship:

../_images/watchface_diagram.png

The abstract class AbstractWathface implements the base features shared across all watchfaces: hands angle computation and animation. It does not define how hands are rendered (i.e, basic shapes, raster images, vector images, etc.).

The VectorWatchface class is an abstract implementation of a watchface that uses Vector Images for drawing the hands. It uses instances of class Hand to render the hands, which vector path representation is loaded from an SvgImage.

FlowerWatchface and SportWatchface are two vector watchface examples. They use custom Hand implementations and add specific animation behavior (i.e, angle computation, opening animation).

9.3.3. How to create a watchface

The creation of a watchface follows these steps:

  1. Add the vector images that represent the hands to the project files.
  2. Create a new widget that extends VectorWatchface.
  3. Set a custom style for the watchface widget (background, etc.).
  4. Integrate the new watchface into the application flow.

Optionally, the watchface may also:

  • Define a custom animation for the seconds.
  • Define a custom animation for the watchface opening.

9.3.3.1. Add the vector hands to the project

As a prerequisite for the following, the vector hands should be available in the SVG format.

Before they can be used in the application, the SVG hands have to be converted into a format that is supported by the platform. MicroEJ provides the SVG2MVG tool for this operation. The use of this tool is covered in the Vector Images section.

The Immutables Objects generated by the SVG2MVG tool are now ready to be added to the project files:

  1. Add the immutables files in the resources folder:
../_images/watchfaces_immutables.png
  1. Immutables Objects are Classpath Elements that have to be declared in a *.immutables.list file:
../_images/watchfaces_list.png

9.3.3.2. Create a new watchface widget

Let’s create a new watchface widget that uses the vector description of the hands:

public class MyWatchface extends VectorWatchface {

    @Override
    protected Hand createHourHand() {
        return new Hand(SvgImage.getSvgImage("mywatchface_hour.svg"), 14, 127); //$NON-NLS-1$
    }

    @Override
    protected Hand createMinuteHand() {
        return new Hand(SvgImage.getSvgImage("mywatchface_minute.svg"), 15, 177); //$NON-NLS-1$
    }

    @Override
    protected Hand createSecondHand() {
        return new Hand(SvgImage.getSvgImage("mywatchface_second.svg"), 7, 163); //$NON-NLS-1$
    }
}

Note

The integer arguments of the Hand constructor specify the coordinates of the center of rotation, relative to the top-left corner of the VG image.

9.3.3.3. Set a custom style for the watchface widget

Modify the CascadingStylesheet used by the demo application to apply custom style attributes to the new watchface widget. In this example, the watchface will have an image as background.

In the class StylesheetFactory, add a new method setMyWatchfaceStyle() that defines a custom style for the watchface, then call it from newStylesheet().

public static Stylesheet newStylesheet() {
    CascadingStylesheet stylesheet = new CascadingStylesheet();

    setGeneralStyle(stylesheet);
    setWatchfaceCarouselStyle(stylesheet);
    setFlowerWatchfaceStyle(stylesheet);
    setSportWatchfaceStyle(stylesheet);

    // defines the style of the watchface widget
    setMyWatchfaceStyle(stylesheet);

    // ... rest of the code omitted ...
}

private static void setMyWatchfaceStyle(CascadingStylesheet stylesheet) {
    // retrieves the style for widgets of type MyWatchface from the stylesheet
    EditableStyle style = stylesheet.getSelectorStyle(new TypeSelector(MyWatchface.class));

    // sets the image "mywatchface-background.png" as background of the watchface
    Image image = WatchImageLoader.IMAGE_LOADER.loadImage("mywatchface-background"); //$NON-NLS-1$
    style.setBackground(new ImageBackground(image));
}

Note

The declaration of the image resource “mywatchface-background.png” used here as background of the watchface is not covered in this document. Image declaration follows the same principles as Immutables Objects declaration, documentation can be found in the Application Developer Guide.

9.3.3.4. Integrate the new watchface into the application flow

Finally, the widget needs to be added to the widget hierarchy of the application in order to integrate into the existing flow.

Let’s create a page that will hold the new watchface widget. Extend the type WatchfacePage:

public class MyWatchfacePage extends WatchfacePage {

    private static final int POSITION = 1;

        public MyWatchfacePage() {
            super(new MyWatchface(), POSITION);
    }
}

Note

The POSITION argument specifies the position of the new watchface in the carousel of watchfaces.

Now, for demonstration purposes, the new watchace will replace the Flower watchface in the demo: to do so, instantiates the new page when the user selects the Flower watchface in the UI. Modify the method resolvePage(String) of the class PageResolver like the following:

public static Container resolvePage(String name) {

    // ... code omitted ...

    switch (name) {
    case PageNames.SPORT_WATCHFACE:
        return new SportWatchfacePage();
    case PageNames.FLOWER_WATCHFACE:
        // for convenience, uses the slot of the flower watchface
        return new MyWatchfacePage();

    // ... rest of the code omitted ...
}

Run the demo application:

../_images/watchfaces_final.png

Congratulations, a new watchface was successfully added to the application!

Optionally, customization of the hands animation may be performed in the next sections.

9.3.3.5. Define a custom animation of the second hand

Animating the hands consists in the definition of the way hands angle change over time. In other words, the angle of an hand is a function of time.

Watchfaces use an instance of WatchAngleComputer to implement this function. The LinearWatchAngleComputer implementation is used by default, it computes hand angles in a linear way. The result is a continuous spin of the hands.

The Sport watchface, however, uses an other implementation, QuarticWatchAngleComputer: the angle of the second hand is a polynomial function, which makes the second hand move quickly at each tick.

In the following example, the second hand will move in discrete ticks rather than continuously to achieve a more classic animation. First step is to create a new implementation of WatchAngleComputer, derived from LinearWatchAngleComputer to inherit from the hour and minute hands behavior:

/**
* An implementation of {@link WatchAngleComputer} that computes the second hands ticks angle.
*/
public class TickWatchAngleComputer extends LinearWatchAngleComputer {

    /** Constant value for the angle between two successive seconds (in degrees). */
    protected static final int DEGREES_SECOND = 6;

    @Override
    public float getSecondAngle(float second) {
        return (int) second * DEGREES_SECOND;
    }
}

Next step is to modify the constructor of the watchface to set TickWatchAngleComputer as the angle computer:

/**
* Creates a new custom watchface.
*/
public MyWatchface() {
    setAngleComputer(new TickWatchAngleComputer());
}

9.3.3.6. Define a custom animation for the watchface opening

The animation when the watchfaces show up is a good way to increase the user experience. It may change temporarily the hands angle computation or even transform the shape of the hands.

By default, watchfaces do not animate the opening. Implementors can set custom animations by overriding the method createOpeningAnimation() and return an array of Animation objects.

@Override
protected Animation[] createOpeningAnimation() {

    // ... the code that constructs the array of animations goes here ...

    return animations;
}

As an example, the Flower watchface changes the hands angle computation, while the Sport watchface scales the hands in a bounce effect.

Finally, enable the opening animation by calling setAnimateOpening(true) in the constructor of the watchface:

/**
* Creates a new custom watchface.
*/
public MyWatchface() {
    setAngleComputer(new TickWatchAngleComputer());
    setAnimateOpening(true);
}