User Interface
Making a user interface toolkit that can adapt to multiple products is hard, but it is even harder in the Garmin ecosystem. The mechanical designs of these products may be very different depending on the use case the product is designed for. As a result, the user interfaces of Garmin devices also vary by product: some have physical buttons, some have coordinate touch screens, and some provide up, down, and action buttons.
Rather than design a user interface toolkit that adapts to what device the UI is running on, the
Connect IQ SDK makes it easier to port the user interface to different products. At the resource compiler level, we include a way to define a page in XML as well as specify page definitions per product. These tools make it easy to support multiple devices with a single app.
Drawables, Views, And Layouts
Watch faces と apps ではページスタック(page stack)をもっている。ヴュー(View)はページを表すオブジェクトである。ヴューはページスタックにプッシュしたちポップされたりする。まさにスタック。
ヴューはtransitionを使って、ページスタック上で別のヴューと入れ替えたりもできる。
View が画面。画面の構成を定義するのが Layout。Layout の要素として配置されるのが Drawables 、といったところか?
本家のページより抜粋
widgets と watch-apps では
- onStart() ← これはAppBase、つまりプログラム本体の関数
- WatchUi.Viewが取り込まれる前に実行される
- onLayout()
- onShow()
- ヴューがForegroundに来た時に実行される。
- onUpdate()
- ここで描画を実行。drawはここで使う。
- onShow()の後、勝手に実行
- WatchUI.requestUpdate()を実行しても、onUpdate()が実行される。
- onHide()
watchfaces と datafields では
- onLayout()
- onShow()
- onUpdate()
class View
{
// ヴューのエントリポイントがonLayout().
// リソースが読み込まれ、レイアウトがセットされる前に呼ばれている。
// @param [Graphics.Dc] dc The drawing context
// @return [Boolean] true if handled, false otherwise
function onLayout( dc );
// When the View is brought into the foreground, onShow() is called.
// @return [Boolean] true if handled, false otherwise
function onShow();
// When a View is active, onUpdate() is used to update dynamic content.
// This function is called when the View is brought to the foreground.
// For widgets and watch-apps it is also called when WatchUi.requestUpdate()
// is called. For watchfaces it is called once a minute and for datafields
// it is called once a second. If a class that extends View does not
// implement this function then any Drawables contained in the View will
// automatically be drawn.
// @param [Graphics.Dc] dc The drawing context
// @return [Boolean] true if handled, false otherwise
function onUpdate( dc );
// Before the View is removed from the foreground, onHide() is called.
function onHide();
// Use setLayout() to set the layout for the View. If the extending class
// does not override onUpdate(), then all Drawables contained in layout
// will automatically be drawn by the View.
// @param [Array] layout An array of Drawables
function setLayout(layout);
}
各ヴューはレイアウト(layout)を持っている。
レイアウトとはDrawablesとよばれる描画オブジェクトの集まりで、各Drawableはビューに置かれる。
Drawableはdrawメソッドを通じて、デバイスコンテキストへ描画される。
どんなDrawableを拡張したオブジェクトも描画オブジェクトで、プロパティはpublicになる。
Monkey C は、基本的はdrawablesとしてレイアウトにリソースとしてText, Bitmapを配置できる。
リソースファイルで Layouts と Drawables を定義する
リソース
Layout と Drawable は「リソース」ファイルとして定義する(することができる、が正しいか)。これにより Monkey C を扱わずとも、特定のデバイス向けのレイアウトを作成することができる。
加えて DrawableList オブジェクトを can also be defined, which are Drawable objects that can draw a number of graphics primitives.
Layout を使う
XML上でレイアウトを定義する場合、描画オブジェクトをレイアウトタグの中に並べればよい。リストに並べられた描画オブジェクトはMonkey Cの描画オブジェクトに変換され、次々と描画される。そのため、2つ以上の描画オブジェクトがあれば、1つ目を描画した上から2つ目が描画されることになる。
レイアウト定義の一例
<resources>
<layout id="MainLayout">
<drawable id="MainBackground" />
<label text="Page Heading" x="10" y="5" font="Gfx.FONT_LARGE" color="Gfx.COLOR_BLACK" />
<label text="Your information goes here." x="10" y="25" font="Gfx.FONT_MEDIUM" color="Gfx.COLOR_DK_GREY" />
</layout>
</resources>
レイアウトリソースの使用方法
- onLayout(dc)内で、setLayout()で登録
- onUpdate(dc)内でView.onUpdate(dc)を呼び出す。ViewのonUpdate(dc)を呼び出さないとほっとかれる。
class MainView {
function onLayout( dc ) {
setLayout( Rez.Layouts.MainLayout( dc ) ); // レイアウトを読み込み
}
function onUpdate( dc ) {
// Call parent’s onUpdate(dc) to redraw the layout
View.onUpdate( dc );
// この下に更にアップデートする必要のあるものを記述
}
}
Layout タグでサポートされる属性
属性 |
定義 |
値 |
初期値 |
補足 |
id |
紐付けるためのID番号This is used to reference the layout in the Rez module |
半角文字で始まる値 |
NA |
必須 |
Drawables (ビットマップファイル、drawable XML リソース) を drawable タグ を使って、レイアウトの中に含めることができる。
属性 |
定義 |
値 |
初期値 |
補足 |
id |
drawable の id The ID provided here references the drawable defined in the resource XML file |
半角文字で始まる値 |
NA |
必須; resource XML file で定義される必要あり |
Text can also be included in layouts. To inlude text use the label tag, which supports the following attributes:
属性 |
内容 |
値 |
初期値 |
補足 |
text |
表示する文字列 |
NA |
An empty string |
|
font |
フォント |
Graphics font constant or the ID of a user-defined font |
Graphics.FONT_MEDIUM |
|
x |
表示X位置 |
ピクセル値、もしくは center |
0 |
|
y |
表示X位置 |
ピクセル値、もしくは center |
0 |
|
justifcation |
How the text should be justified in relation to the X & Y location |
Graphics text justify constant |
Graphics.TEXT_JUSTIFY_LEFT |
|
color |
The color of the text |
Graphics color constant or a 24-bit integer of 0xRRGGBB 形式 |
The current draw context’s foreground color |
|
Drawables
Drawable XML resources consist of a list of basic drawables: bitmaps and shapes. To create an XML drawable, define a <drawable-list> in an XML resource file. Both <bitmap> and <shape> tags should be be placed as child nodes inside the <drawable-list>. An example drawable-list:
<resources>
<drawable-list id="Smiley" background="Gfx.COLOR_YELLOW">
<shape type="circle" x="10" y="10" radius="5" color="Gfx.COLOR_BLACK" />
<shape type="circle" x="30" y="10" radius="5" color="Gfx.COLOR_BLACK" />
<bitmap id="mouth" x="15" y="25" filename="../bitmaps/mouth.png" />
</drawable-list>
</resources>
To use this drawable in code do the following:
function onUpdate( dc ) {
var mySmiley = new Rez.Drawables.Smiley();
mySmiley.draw( dc );
}
The <drawable-list> tag supports the following attributes:
Attribute Definition Valid Values Default Value Notes
id The ID of the drawable Any string that starts with a character NA Required
x The X coordinate of the top left corner in relation to the parent element pixel value or center 0
y The Y coordinate of the top left corner in relation to the parent element pixel value or center 0
width The width of the drawable list. pixel value or fill fill
height The height of the drawable list. pixel value of fill fill
foreground The color of elements (shapes and text) drawn in this layout Graphics color constant or a 24-bit integer of the form 0xRRGGBB The current draw context’s foreground color
background The background color of this layout Graphics color constant or a 24-bit integer of the form 0xRRGGBB Gfx.COLOR_TRANSPARENT
The <shape> tag supports the following attributes:
Attribute Definition Valid Values Default Value Notes
type The type of the shape to be drawn rectangle, ellipse, circle, or polygon NA Required
x The X coordinate of the top left corner in relation to the parent element pixel value or center 0
y The Y coordinate of the top left corner in relation to the parent element pixel value or center 0
points A list of points which defines the polygon
x1, y1], [x2, y2], ... , [xN, yN NA Required for polygon; must have at least 3 points
width The width of the shape to be drawn pixel value or fill fill Only valid for ellipse and rectangle
height The height of the shape to be drawn pixel value or fill fill Only valid for ellipse and rectangle
color The color of the shape to be drawn Graphics color constant or a 24-bit integer of the form 0xRRGGBB The current draw context’s foreground color
corner_radius The radius of the rounded corners of the rectangle pixel value 0 Only valid for rectangle
radius The radius of the circle pixel value 0 Required for circle
border_width The width of the border around the shape pixel value 0 Only valid for rectangle, ellipse, and circle
border_color The color of the border around the shape Graphics color constant or a 24-bit integer of the form 0xRRGGBB The current draw context’s foreground color Only valid for rectangle, ellipse, and circle
The <bitmap> tag supports the following attributes:
Attribute Definition Valid Values Default Value Notes
id The ID of the drawable Any string that starts with a character NA Required
x The X coordinate of the top left corner in relation to the parent element pixel value or center 0
y The Y coordinate of the top left corner in relation to the parent element pixel value or center 0
filename The relative path to the image that should be shown A valid, relative path NA Required
Input Handling
As if input handling wasn’t already one of the most important and complicated pieces of a UI toolkit, Garmin devices take the complication up a level. Unlike those touchable glowing rectangles that are modern smart phones, Garmin devices come in lots of shapes and sizes. Touch screens are not always ideal for all watch products, so there is a mix of input styles and screen technologies. It’s the job of the UI toolkit to make this coherent to the developer.
Input and App Types
Not all app types have access to input. Watch faces and data fields cannot handle input, though widgets can receive input (may be limited on some devices) and watch-apps will have the most input capability.
Input Delegates
The Delegate object is an object that implements a certain interface specific to input handling. Monkey C provides a low level InputDelegate that allows handling of events at a basic level. This is good when the app needs to handle button presses or screen presses in a particular way.
module WatchUi
{
// This class implements basic input handling. A developer needs to override
// the events they want to handle.
class InputDelegate
{
// Key event
// @param evt KEY_XXX enum value
// @return true if handled, false otherwise
function onKey( evt );
// Click event
// @param evt Event object with click information
// @return true if handled, false otherwise
function onTap( evt );
// Screen hold event. Sent if user is touching
// the screen
// @param evt Event object with hold information
// @return true if handled, false otherwise
function onHold( evt );
// Screen release. Sent after an onHold
// @param evt Event object with hold information
// @return true if handled, false otherwise
function onRelease( evt );
// Screen swipe event
// @param evt Event
function onSwipe( evt );
}
}
Behaviors
Garmin makes products with a purpose, and that purpose can alter the design of one product line over another. Deciding whether a product has a touch screen or has buttons can depend on the environment in which the user will take. For instance, if the product is intended to be used in water (swimming, canoeing, on a boat), it may not have a touch screen. These decisions make for superior products, but also add to developer frustration due to device fragmentation.
Most products will support common behaviors (next page, back a page), but how they are executed by the user may differ based on the available input types. To help with this dilemma, Monkey C exposes events at a behavior level. Behaviors separate high-level intentions from the actual input type—next page versus screen press. The BehaviorDelegate is a super class of InputDelegate and maps its low level inputs to common operations across multiple products. Using the BehaviorDelegate can lead to much more portable code.
class BehaviorDelegate extends InputDelegate
{
// When a next page behavior occurs, onNextPage() is called.
// @return [Boolean] true if handled, false otherwise
function onNextPage();
// When a previous page behavior occurs, onPreviousPage() is called.
// @return [Boolean] true if handled, false otherwise
function onPreviousPage();
// When a menu behavior occurs, onMenu() is called.
// @return [Boolean] true if handled, false otherwise
function onMenu();
// When a back behavior occurs, onBack() is called.
// @return [Boolean] true if handled, false otherwise
function onBack();
// When a next mode behavior occurs, onNextMode() is called.
// @return [Boolean] true if handled, false otherwise
function onNextMode();
// When a previous mode behavior occurs, onPreviousMode() is called.
// @return [Boolean] true if handled, false otherwise
function onPreviousMode();
}
Built In Handlers
In general, complicated input from users and feedback to users should not be done on a wearable device, but should instead be done via a phone app or web page. However, sometimes the device needs to get some form of user input or provide confirmation to the user.
WatchUi provides three widgets to handle input: menus, the generic picker, and the number picker (the number picker has been deprecated in favor of the generic picker).
Two additional handlers provided by WatchUi can be used to give feedback to the user: the confirmation dialog and progress dialog.
Menu
A menu is a list of options for the user. The options are displayed in a list that matches the device the app is running on. A menu can be defined in the resource XML file using the following format:
<menu id="MainMenu">
<menu-item id="item_1">Item 1</menu-item>
<menu-item id="item_2">Item 2</menu-item>
</menu>
The resource compiler will then take this XML and generate a Menu object in the Rez module. In order to use this menu, a developer simply needs to push the menu and a delegate for the menu using Ui.pushView():
class MyClass extends Ui.View {
function openTheMenu() {
Ui.pushView( new Rez.Menus.MainMenu(), new MyMenuDelegate(), Ui.SLIDE_UP );
}
}
class MyMenuDelegate extends Ui.MenuInputDelegate {
function onMenuItem(item) {
if ( item == :item_1 ) {
// Do something here
} else if ( item == :item_2 ) {
// Do something else here
}
}
}
Generic Picker
The Picker class, along with the PickerDelegate and PickerFactory classes, provides applications with the ability to create onscreen lists of user-selectable objects. A picker consists of one or more choosable objects, a title, a next and previous arrow, and a confirm button. The next and previous arrows and the confirm button are device specific but can be overwritten if desired. Pickers are pushed using WatchUi.pushView(), providing a PickerDelegate for the input delegate. A PickerFactory is required to indicate what should be displayed for each pickable value.
Sample picker implementations can be found in the Picker sample project, inside the samples/Picker directory.
User Interface
[Main components of a generic picker layout]
The above image is a representation of the general structure of what a picker should look like on a square screen. Other screen formats should have the same layout with some size differences to account for the screen and button layout.
The top red bar represents where the title of the picker is displayed.
Up and down arrows to scroll through the available options are placed where the green boxes are.
The leftmost blue box is where the last item you selected is shown if the picker has multiple selectable items.
The center blue box is the item you are currently selecting.
The white box will either be the next selectable item in the list or the button to confirm your selection.
Development Interface
class Picker extends Toybox.WatchUi.View
{
// Constructor
// @param [Dictionary] options @see Picker#setOptions
function initialize( options );
// Set the options for the Picker.
// @param [Dictionary] options the options for the Picker
// @option options [Drawable] :title the title for the Picker. Required.
// @option options [Array] :pattern an Array of [Object] for the Picker to display. If the Array entry is a PickerFactory then it is presented to the user to make a choice. If it is a Drawable then it is display only. Required.
// @option options [Array] :defaults an Array of [Number] indicating the starting index for each entry in :pattern. Optional.
// @option options [Drawable] :nextArrow a custom next icon for the Picker. Optional.
// @option options [Drawable] :previousArrow a custom previous icon for the Picker. Optional.
// @option options [Drawable] :confirm a custom confirm icon for the Picker. Optional.
function setOptions( options );
}
class PickerDelegate
{
// Handle a confirm event from a [Picker]
// @param [Array] values The chosen values from the Picker. For [Drawable] entries, null will be returned for that value.
function onAccept( values );
// Handle a cancel event from a [Picker]
function onCancel();
}
class PickerFactory
{
// Generate a Drawable instance for a given item
// @param [Number] item Item index
// @param [Boolean] isSelected true if the current item is the selected item, false otherwise
// @return [Drawable] object to be rendered
function getDrawable( item, isSelected );
// Return a representative value for this item
// @param [Number] item Item index
// @return [Object] Representative item for the given index
function getValue( item );
// Get the number of items in the factory
// @return [Number] number of items in the factory
function getSize();
}
Number Picker - DEPRECATED
To allow the user to pick or adjust numerical data, the number picker widget is your best bet. The number picker widget allows for editing or adjusting of common numerical types. Here’s an example of number picker use:
enum
{
NUMBER_PICKER_DISTANCE, // A Float in meters
NUMBER_PICKER_TIME, // A Duration
NUMBER_PICKER_TIME_MIN_SEC, // A Duration
NUMBER_PICKER_TIME_OF_DAY, // A Duration representing the number of seconds since midnight
NUMBER_PICKER_WEIGHT, // A Float in kilograms
NUMBER_PICKER_HEIGHT, // A Float in meters
NUMBER_PICKER_CALORIES, // A Number
NUMBER_PICKER_BIRTH_YEAR // A Number
}
class NumberPicker
{
// Constructor
// @param mode An enum value of type NUMBER_PICKER_*
// @param initialValue The initial value for the Number Picker, type
// depends on mode
function initialize( mode, initialValue );
}
class NumberPickerDelegate
{
// When a number is chosen, onNumberPicked() is called, passing the
// chosen value.
// @param value The chosen number, type depends on the NumberPicker mode
function onNumberPicked( value );
}
Confirmation Dialog
The confirmation dialog provides a simple yes/no dialog. This is useful when presenting a simple selection.
enum
{
CONFIRM_NO,
CONFIRM_YES
}
class Confirmation
{
// Constructor
// @param [String] message A textual confirmation message
function initialize( message );
}
class ConfirmationDelegate
{
// When a response is chosen, onResponse() is called, passing
// the response of CONFIRM_NO or CONFIRM_YES
// @param response The response of the confirmation of CONFIRM_NO or CONFIRM_YES
function onResponse( response );
}
Progress Bar
The progress dialog gives a standard wait dialog. It has two modes—one that shows the completion of some process, and a second that acts a wait timer displaying an indeterminate amount of progress. The look and feel of the progress bar will be device-specific.
class ProgressBar
{
// Constructor
// @param [String] displayString The string to display on the progress bar view
// @param [Float] startValue The initial value for the progress bar (0-100), null for "busy"
function initialize( displayString, startValue );
// Use setProgress() to set the value of the progress bar.
// @param [Float] progressValue The value for the progress bar (0-100), null for "busy"
function setProgress( progressValue );
// Use setDisplayString() to set the string displayed on the progress bar view.
// @param [String] displayString The string to display on the progress bar view
function setDisplayString( displayString );
}
最終更新:2018年04月08日 22:44