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
.
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:
To create a watchface project, the 3 following steps have to be followed:
- Creating the module project
- Adding the required resources to the project
- Adding the required classes to the project
- Integrating the watchface on the device
10.3.2.1. Project Setup
- Select
File
>New
>Project...
. - Select
MicroEJ
>Module Project
and pressNext >
. - Fill in each field, select skeleton
microej-javalib
and pressFinish
. - 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:
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:
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
andMySecondHand
: 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:
- Right-click on the entry point class in the project explorer and select
Run As
>Run Configurations...
. - Right-click on
MicroEJ Application
and selectNew
. - In the
Execution
tab, select theMIMXRT595
platform. - Tick
Execute on Device
and selectBuild Dynamic Feature
in theSettings
listbox. - In the
Configuration
tab, select theFeature
>Dynamic Download
page. - 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:
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:
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);
}