Garminウォッチフェイスの作成:複数デバイスへの対応

Garminウォッチフェイスの作成:作ったウォッチフェイスを公開

上記で公開したウォッチフェイスは、次の3種類のものに対応している。

  1. ForeAthlete 45の様に丸い形状。ただし、PartialUpdateがないもの。
  2. Vivoactive 4の様に、丸い形状でPartialUpdateに対応しているもの。
  3. Vivoactive HRの様に長方形のもの。

また1の種類のものに関しては、フォントサイズが異なるものも個別に対応している。

これら実装方法についての説明。

種類ごとの実装の分け方

基本的な機能をベースクラスに押し込め、デバイスの種類ごとに異なる実装があるものを、ベースクラスから派生したクラスで実装するようにした。

PlantUML Syntax:</p>
<p>WatchFace <|– DigitalViewBase</p>
<p>DigitalViewBase : onEnterSleep()</p>
<p>DigitalViewBase : onExitSleep()</p>
<p>DigitalViewBase : doViewSec()</p>
<p>DigitalViewBase <|– Digital45View</p>
<p>Digital45View : onUpdate()</p>
<p>DigitalViewBase <|– DigitalHRJView</p>
<p>DigitalHRJView : onUpdate()</p>
<p>Digital45View <|– DigitalVivo4View</p>
<p>DigitalVivo4View : onPartialUpdate()</p>
<p>DigitalVivo4View : doViewSec()</p>
<p>WatchFaceDelegate <|– DigitalVivo4Delegate</p>
<p>DigitalVivo4View o– DigitalVivo4Delegate</p>
<p>

DigitalViewBase

ウォッチフェイス制御用の基本クラス。
日の出・日没の計算などの共通する処理をここに押し込めている。

特色としては、高電力モード、低電力モードのイベントであるonEnterSleep()/onExitSleep()を処理し、現在どちらのモードなのかをこのクラス内で処理している。
その結果については、doViewSec()メソッドで受け取れるようにしており、リターンがtrueの場合、高電力モードで秒の描画を行うように処理している。

doViewSec()をメソッド化したのは、DigitalVivo4ViewでPartialUpdateで秒の描画を行うため、その処理の切り分けを行いたいためにメソッド化し、DigitalVivo4View側でオーバーライドしたかったため。

Digital45View

丸い形状のウォッチフェイスを描画するための基本クラス。
処理の大まかなところは、Garminウォッチフェイスの作成:デジタル時計3で作成したものを踏破している。

ただしいろいろなデバイス、フォントに対応するため、initialize()でフォントの情報を決定したら、そのフォントの大きさを求め、テキストの描画位置などを計算で一括で求めるように変更してある。

実装的には以下のようなものを設けている。

	var y;				// Y座標

    function initialize() {
        DigitalViewBase.initialize();
		// テキストフォントの指定
    	// 機種はfr45
        fnLarge = [getFont(Rez.Strings.FontLarge)];		// 画面高さ比 0.38 近辺のフォント
        fnNormal = [getFont(Rez.Strings.FontNormal)];	// 画面高さ比 0.16 近辺のフォント
    }

    // レイアウト計算
    function calcLayout(dc) {
		// テキストフォントサイズの取得
        calcFontSize(fnLarge);
        calcFontSize(fnNormal);

		widthSec = dc.getTextWidthInPixels("00", fnNormal[0]) + 2;
		viewSecX = center[0] + (dc.getTextWidthInPixels("00:00", fnLarge[0]) + 2 + widthSec) / 2.0 - widthSec;

		// 1段目は月・日・曜日
		y = [center[1] - fnLarge[1] - fnNormal[1] + 5];
		// 2段目は時分
		y.add(center[1] - fnLarge[1]);
		// 3段目は秒
		y.add(center[1] - fnNormal[1]);
		// 4段目はステップ数
		y.add(center[1]);
		// 5段目はHRと日没、日の出時刻
		y.add(center[1] + fnNormal[1]);
		// 6段目はバッテリー
		y.add(center[1] + fnNormal[1] * 2);

		// ビットマップの位置は個別に計算
		xyNotify = [dc.getWidth() * 0.0673, y[3] + (fnNormal[2] - bmNotify.getHeight()) / 2];
		xyBluetooth = [dc.getWidth() * 0.1442, y[4] + (fnNormal[2] - bmBluetooth[2]) / 2];
    }

    // 描画情報が決定後のリソースなどの計算用
    function onLayout(dc) {
    	DigitalViewBase.onLayout(dc);
		calcLayout(dc);
    }

initialize()で、フォントをリソースから取得したのち、onLayout()でdcが決定した後、calcLayout()でそれぞれの描画位置を計算している。

calcFontSize()はDigitalViewBaseのメソッドで、渡されたArray形式の先頭に入っているフォントからフォントの高さを[1]と[2]に詰めて返すというもの。ここでフォントの高さを求め、テキスト等のY座標値を求めている。

DigitalHRJView

長方形のウォッチフェイスを描画するための基本クラス。
こちらは、新たに作成したが、基本的にはDigital45Viewと同じ。

違うところは、24時間のアナログ計を描画しないところ、日の出・日の入りは両方とも表示するところ、フロアの昇降階数を表示するところ。

表示するテキストやビットマップの位置も、initialize()でフォントの情報を決定した後、そのフォントの大きさから描画座標を決定するようにしてある。

ただ、ちょっと調べてみたところ長方形のスマートウォッチでウォッチフェイスを切り替えられるものは、Vivoactive HRぐらいしかなかったので、複数デバイス対応のため、フォントサイズによる自動計算の処理を入れなくてもよかったかも。

DigitalVivo4View

onPartialUpdate()を実装し、秒の表示のみ部分描画するようにしている。

ただ、onPartialUpdate()での描画時間が一定時間を超えると、onPartialUpdate()の呼び出しがなくなるので、その対応を行うためDigitalVivo4Delegateを用意しDigitalVivo4Delegate::onPowerBudgetExceeded()が呼び出された場合、Digital45Viewと同じ動作をさせるようにする。

それを実現するため、doViewSec()をオーバーライドし、onPowerBudgetExceeded()が呼び出されていない間は秒の描画モード用のレイアウトにするようにしている。

メイン側でのオブジェクトの生成方法について

DigitalViewBase派生のオブジェクトをデバイスごとに切り分ける方法は以下の通り。

    // Return the initial view of your application here
    function getInitialView() {
    	var model = WatchUi.loadResource(Rez.Strings.DeviceModel);
    	System.println(model);
    	switch(model) {
    	case "vivoactive_hr":
    		return [ new DigitalHRJView() ];
    		break;
		default:
			if (WatchUi.WatchFace has :onPartialUpdate) {
				var delegate = new DigitalVivo4Delegate();
				return [ new DigitalVivo4View(delegate), delegate ];
			}
			else {
				return [ new Digital45View() ];
			}
			break;
		}
    }

デバイスの切り分けは、Garminウォッチフェイスの作成:デバイスの認識を利用。
デバイスがPartialUpdateを保持しているかどうかについては、hasを使用して判別している。

Vivoactive HRについては調査したところPartialUpdateを保持していなかったので、hasの判断はしていない。

同一形状での異フォント対応

フォントをリソースで読み込み、それに合わせてXY座標値を計算するような形式にした。

ベースで作成したのがForeAthlete 45で、使用したフォントがNUMBER_MEDIUMとLARGE。
それぞれの画面比の大きさについては、Garminウォッチフェイス作成:フォントサイズで調べたので、似た比率のフォントをピックアップし、それをリソースで指定するようにした。
ちなみに、ここで似合うフォントがない場合、対応機種から外した。

またリソースでGraphics.FONT_*を直接指定する方法を見つけることができなかったため、リソース側は文字列で指定し、それをGraphics.FONT_*に変換するメソッドを新たに追加して利用するようにした。

<resources>
<strings>
<string id="FontLarge">NUMBER_MEDIUM</string>
<string id="FontNormal">LARGE</string>
</strings>
</resources>
    // リソースの文字列からフォント番号を取得する
    function getFont(resid) {
    	switch(WatchUi.loadResource(resid)) {
    	case "XTINY":
    		return Graphics.FONT_XTINY;
		case "TINY":
    		return Graphics.FONT_TINY;
		case "SMALL":
    		return Graphics.FONT_SMALL;
		case "MEDIUM":
    		return Graphics.FONT_MEDIUM;
		case "LARGE":
    		return Graphics.FONT_LARGE;
		case "NUMBER_MILD":
    		return Graphics.FONT_NUMBER_MILD;
		case "NUMBER_MEDIUM":
    		return Graphics.FONT_NUMBER_MEDIUM;
		case "NUMBER_HOT":
    		return Graphics.FONT_NUMBER_HOT;
		case "NUMBER_THAI_HOT":
    		return Graphics.FONT_NUMBER_THAI_HOT;
    	}
    }

上がリソースの定義で、下が変換メソッド。変換メソッドはDigitalViewBaseに実装している。

最後に

DigitalVivo4ViewとDigitalVivo4Delegateの実装方法についてはちょっと悩みがあった。
サンプルのAnalogではグローバル変数で情報のやり取りをしていたので、それが普通なのか?と感じたため。

ただ、なんかしっくりこなかったので、ここではグローバル変数での情報のやり取りをした実装はしなかった。
個人的に余計分かりづらくなると思ったので。

コメント

タイトルとURLをコピーしました