ガーミンウォッチのセンサー情報を見てみたいという欲求が出てきたので、それを表示するためのアプリを作成することにした。
アプリのタイプとしては、簡単に起動できるウィジェットとすることにした。
今回、ウィジェットを作るうえでいろいろ調べたことを紹介しようと思う。
使ったクラスは、GlanceViewとViewLoop。
GlanceViewはウィジェットで必要なクラスだがViewLoopはマルチページビューなものを簡単に作れるサポートクラス的なもの。
作ったソースは以下にとりあえず公開。
プログラムの構築方法と参考としたサンプル
VSCode上からはMonkey C:New Projectからプロジェクトタイプとしてwidgetを選ぶ。

これで生成されるのは、AppBase派生のアプリケーションクラスとView派生のビュークラス。
ただこれだけで構築していくと、アプリと同等の起動しかできず、ウィジェットの一覧への表示がされない。
ウィジェットの一覧表示とはこんな形の物。
ここに表示させるためにはグランス機能を実装する必要がある。
グランス機能を使うにはAPI Level 3.1.0以上である必要がある。
ウィジェットを作るには、まとめると。
- App TypeをWidgetにする。
- Minimum Supported API Levelを3.1.0以上にする。
また、今回1センサー情報を1ページで表示するため、複数ページのデータを表示するようにしてみた。この表示にはViewLoopという機能を使っている。
センサー情報を取り出すためのサンプルコードは、ガーミンSDK内のサンプルフォルダSensorを、グランス表示の実装ヒントとしてこちらを参考にした。
グランス表示(GlanceView)
まずは、ウィジェット一覧に表示するためのグランス表示について。
詳細はコアトピックを参照することになるのだが、簡単に言うとウィジェット一覧用の小ぶりなビューをAppBase.getGlanceView()でリターンするように実装するとなる。
リターンさせるのはGlanceView派生とGlanceViewDelegate(派生)。
通常のビューはAppBase.getInitialView()でリターンさせる。ウィジェット一覧での表示用ビューは呼び出し位置が異なることになる。
ウィジェット一覧で表示されるのは、固定エリアと描画エリア。

左側が固定エリアでアプリに指定されているLauncherIconが表示され、右の黄色い領域がGlanceViewに渡されるエリアになる。
ちなみにこの領域はforerunner 165で、width 261, height 124という大きさだった。
なおGlanceViewの大きさが記載されているドキュメントが無いので、自分で調べないといけないみたいだ。また過去のフォーラムQAで、シミュレーターと実機でサイズが異なるというのがあったので、設計としてはある程度余裕を持ったものにする必要があるのではないかと思う。
注意点としては以下の通り。
- グランスビュー内で利用可能なメモリはシビア
forerunner 165のシミュレーターで確認したところ、59.9kByteだった。
通常ビューでは763.6kByteなんで10倍以上違う。 - グランスとして呼び出されるところに関しては、(:glance) というアノテーション記述を入れる必要がある。
コアトピック参照。 - 呼び出せるリソースのスコープに注意
xml記述内に、scope=”glance”と入れておく必要がある。無いとアクセスできない。(例外が発生する)
コアトピックのResource Scopesを参照。 - onUpdateのタイミング
ウィジェット一覧をスクロールしている段階でも呼び出しが入るので、遅い処理をするとこのスクロール時間も遅くなる。
出来るだけ早い処理を心掛ける必要がある。
実装
今回の実装では、ViewとGlanceViewで呼び出される実際の描画処理は共通化した。
グランスビュー側でもセンサーから取り出した情報をグラフ表示させたかったので、その部分を共通で利用したかったため。
GlanceViewにViewを渡して、GlanceViewないからViewの各処理(initialize, onLayout, onUpdate)をそのまま呼び出すようにしている。
View側ではGlanceViewからの呼び出しかそうでないかをフラグで持たせて、実際の表示処理を変えるようにしている。
ビューループ表示(ViewLoop)

上のようにUP/DOWNボタンで異なるビューを切り換える機能をシステムで用意したというもの。
ViewLoopを使わない場合は、Delegateを用意しボタン操作に対してのアクションクラスとそれに対応してビューを切り換えるという処理を組み込む必要があった。
ViewLoopに関してはコアトピックに記述がなく、クラスから実装を追って行く必要があったのでちょっと面倒だった。
基本的に作成するのはViewLoopFactory派生のみ。
API Level 3.4.0以上必要となる。
作成したクラスのオブジェクトをViewLoopに渡し、そのオブジェクトをAppBase.getInitialView()のリターンとして返すようにする。
ViewLoopFactory派生のほうで注意しないといけないのは、ViewLoopFactory.getView()のリターンには必ずDelegateを含めないといけない点。
Delegateが必要なくても、new WatchUi.BehaviorDelegate()を配列2番目に入れて返さないと、例外が発生してしまう。
次に、AppBase.getInitialView()側での呼び出しテンプレ的なもの。
function getInitialView() as [Views] or [Views, InputDelegates] {
if (WatchUi has :ViewLoop) {
var factory = new EnvironIndicatorFactory();
var viewLoop = new WatchUi.ViewLoop(factory, {:page => 0, :wrap => true, :color => Graphics.COLOR_GREEN});
// 特殊なページ操作を行う場合はdelegateを独自作成して呼び出す
return [viewLoop, new ViewLoopDelegate(viewLoop)];
} else {
var view = new EnvironView(0);
return [view, new EnvironDelegate(view)];
}
}
複数デバイス対応を行うケースで書いたもので、ViewLoopが無い場合にViewとボタン操作を可能とするDelegateのペアで呼び出すという形にしておく必要がある。
問題点
- ページを現すインジケーターが表示がビルトインアプリは〇で表示されているのだが、ViewLoopはウォッチフェイス選択時のものと同様に、左側に円弧表示されるタイプのものになる。

これに関しては現状回避方法が無いようだ。 - ページ切り替え時にonUpdateが複数回呼び出される。
これは調べた結果、インジケーター表示がある場合、その更新のために呼び出されていた。
まずインジケーター表示がある状態で一回。
インジケーター表示が消える際に1回という感じ。
コメント