10.3. Watchfaces Implementation

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

10.3.1. Overview

The demo implements 4 watchfaces: Sport, Flower, FlowerLP and Simple.

../_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).
  • FlowerLP has no animation when time changes to illustrate Low Power.
  • Simple is installed dynamically to illustrate Additional Watchface.

10.3.2. Creating a Watchface Project

Example of a watchface project:

../_images/watchface_project.png

To create a watchface project, the 3 following steps have to be followed:

  1. Creating the module project
  2. Adding the required resources to the project
  3. Adding the required classes to the project
  4. Integrating the watchface on the device

10.3.2.1. Project Setup

  1. Select File > New > Project....
  2. Select MicroEJ > Module Project and press Next >.
  3. Fill in each field, select skeleton microej-javalib and press Finish.
  4. Open the module.ivy file at the root of the new project and replace the dependencies by:
<dependencies>
        <dependency org="ej.api" name="edc" rev="1.3.3"/>
        <dependency org="ej.api" name="bon" rev="1.4.0"/>
        <dependency org="ej.api" name="kf" rev="1.4.4"/>
        <dependency org="ej.api" name="microui" rev="3.0.3"/>
        <dependency org="ej.api" name="drawing" rev="1.0.2"/>
        <dependency org="com.microej.api" name="vivante-vglite" rev="0.6.0"/>
        <dependency org="ej.library.ui" name="mwt" rev="3.1.3"/>
        <dependency org="com.microej.demo.watch" name="util" rev="1.0.0"/>
</dependencies>

10.3.2.2. Required Resources

Example of the resources of a watchface project:

../_images/watchface_resources.png

The watchface project should embed at least 2 bitmap images: the background of the watchface and a preview to display on the watchface carousel. These images have to be listed in the .images.list file.

The watchface project should also embed at least 3 immutable files representing VG paths: one for each hand of the watchface. These immutable files have to be listed in the .immutables.list file.

Finally, if the watchface is a sandboxed watchface, it should have two additional files: a certificate file (.cert) and a KF module description file (.kf). The KF module description file should include the name and the version of the module, the fully qualified name of the entry point class and the list of classes defined in the project.

Here is an example of a KF module description file:

name=SimpleWatchface
entryPoint=com.microej.demo.watch.watchface.simple.SimpleWatchfaceEntryPoint
types=com.microej.demo.watch.watchface.simple.*
version=1.0.0

10.3.2.3. Required Classes

Example of the classes of a watchface project:

../_images/watchface_classes.png

The watchface project should define at least the following classes:

  • MyWatchfaceEntryPoint: the entry point of the KF feature, which registers the watchface app.
  • MyWatchfaceApp: the watchface app, which shows the watchface desktop when it is started.
  • MyWatchfaceDesktop: the watchface desktop, which shows the watchface widget and sets their style.
  • MyWatchface: the watchface widget, which shows the hands and optional complications and animates them.
  • MyHourHand, MyMinuteHand and MySecondHand: the vector paths of each hand of the watchface.

10.3.2.4. Project Integration

The watchface can be integrated on the device in two different manners, depending on whether the watchface is a built-in watchface or a sandboxed watchface.

In the case of a built-in watchface, add a dependency to the watchface module in the module.ivy file of the com.microej.demo.watch.vglite project. Then, edit the main() method of the Demo class to add the watchface to the watchfaces registry:

watchfacesRegistry.addWatchface(new SimpleWatchfaceApp());

In the case of a sandboxed watchface, create a launcher which will build the binary to upload on the device. This launcher has to be executed after building the kernel (by running the Demo (Emb) launcher). The launcher can be created by following these steps:

  1. Right-click on the entry point class in the project explorer and select Run As > Run Configurations....
  2. Right-click on MicroEJ Application and select New.
  3. In the Execution tab, select the MIMXRT595 platform.
  4. Tick Execute on Device and select Build Dynamic Feature in the Settings listbox.
  5. In the Configuration tab, select the Feature > Dynamic Download page.
  6. Browse the kernel binary file generated by IAR (by default, mimxrt595_freertos-bsp/projects/microej/iar/flash_debug/microej_freertos.out).

See Compile Additional Watchface for an example with the Simple watchface.

10.3.3. Watchface Entry Point

If the watchface is not a built-in watchface of the kernel and is intended to be installed dynamically, the project has to provide an entry point. This entry point has to implement the start() and stop() APIs of FeatureEntryPoint, which are called when the application is installed and uninstalled. The entry point is not used and not required if the watchface is a built-in watchface of the kernel.

The typical implementation for a watchface application is to register the watchface in the start() method. This can be done by calling the registerWatchface() method of the NavigationService, which is provided by the kernel. Is it not required to unregister the watchface in the stop() method as it is done automatically by the kernel when the application is uninstalled.

Here is an example of a watchface entry point:

    public class SimpleWatchfaceEntryPoint implements FeatureEntryPoint {

            @Override
            public void start() {
                    NavigationService navigationService = KernelServiceRegistry.getServiceLoader().getService(NavigationService.class);
                    navigationService.registerWatchface(new SimpleWatchfaceApp());
            }

            @Override
            public void stop() {
                    // do nothing
            }
    }

Note

The KF module description file created earlier (.kf) should point to this class.

10.3.4. Watchface App

The watchface app gives information on the watchface and manages its lifecycle. It is registered by the kernel in the case of a built-in watchface or registered by the entry point in the case of a sandboxed watchface.

The watchface app has to implement the 4 APIs of WatchfaceApp:

  • getName(): returns a string representing the name of the watchface, which is displayed in the watchfaces carousel. The typical implementation is to return a hardcoded string.
  • drawPreview(): draws a preview of the watchface, which is displayed in the watchfaces carousel. The typical implementation is to draw an image which has been loaded from the resources of the application.
  • start(): starts the watchface so that it can show its UI. The typical implementation is to create the watchface desktop and show it on the display.
  • stop(): stops the watchface so that it can release the resources that have been allocated while the watchface was started. The typical implementation is to hide the watchface desktop from the display.

Here is an example of a watchface app:

    public class SimpleWatchfaceApp implements WatchfaceApp {

            private Desktop desktop;

            @Override
            public String getName() {
                    return "SimpleWatchface";
            }

            @Override
            public void drawPreview(GraphicsContext g) {
                    try (ResourceImage image = WatchImageLoader.loadImage("simple_preview")) {
                            Painter.drawImage(g, image, 0, 0);
                    }
            }

            @Override
            public void start() {
                    this.desktop = new SimpleWatchfaceDesktop();
                    this.desktop.requestShow();
            }

            @Override
            public void stop() {
                    if (this.desktop != null) {
                            this.desktop.requestHide();
                            this.desktop = null;
                    }
            }
    }

10.3.5. Watchface Desktop

The watchface widget is shown on the display using a desktop. The watchface desktop should extend WatchfaceDesktop rather than Desktop in order to handle the events which allow the user to return to the watchfaces carousel. The desktop implementation should create the watchface widget and the stylesheet.

Here is an example of a watchface desktop:

    public class SimpleWatchfaceDesktop extends WatchfaceDesktop {

            public SimpleWatchfaceDesktop() {
                    super(new SimpleWatchface(), createStylesheet());
            }

            private static Stylesheet createStylesheet() {
                    CascadingStylesheet stylesheet = new CascadingStylesheet();

                    EditableStyle style = stylesheet.getSelectorStyle(new TypeSelector(SimpleWatchface.class));
                    Image backgroundImage = WatchImageLoader.loadImage("simple_background");
                    style.setBackground(new ImageBackground(backgroundImage));

                    return stylesheet;
            }
    }

Note

The declaration of the image resource “simple_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.

10.3.6. Watchface Widget

10.3.6.1. Architecture

Watchfaces are widgets. Several types were introduced to model the specificity of watchface widgets. These types are found in the com.microej.demo.watch.util library:

The figure below helps understanding the responsibility of these types 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).

10.3.6.2. Adding 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:

First, add the immutables files in the resources folder:

../_images/watchface_immutables.png

Finally, Immutables Objects are Classpath Elements that have to be declared in a *.immutables.list file:

immutables/simple_second.xml
immutables/simple_minute.xml
immutables/simple_hour.xml

10.3.6.3. Creating 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.

10.3.6.4. Defining 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());
}

10.3.6.5. Defining 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);
}