今持っているGarminのForeAthlete 45で、気に入ったウォッチフェイスがなかったので、とりあえず作ってみた。
仕様みたいなもの
まず、ウォッチフェイスに必要な情報。
- 時:分:秒
- 曜日・日付
- 通知があるかないか
- Bluetooth接続状況
- バッテリー残容量
- 歩数/歩数ゴール
- 心拍数
あったらいいなと思う情報
- 上昇階数
- 日が出ている時間と今の時間
- 歩数ゴールまでの到達割合
ちなみに上昇階数は、利用できるSDKのバージョンにより、45では利用できないことが後で分かった。また秒の表示は、45では常時表示できないようだ。
画面構成としては、以下のような感じ。
冬の間、袖の下に時計が隠れ、左手に時計を巻いていると、パッと見た瞬間は右半分しか視認できないので、その部分に特に確認したい項目を配置してみた。
実装内容
ソースコード全体
using Toybox.WatchUi;
using Toybox.Graphics;
using Toybox.System;
using Toybox.Lang;
using Toybox.Time;
class DigitalView extends WatchUi.WatchFace {
var center;
var bmBluetooth;
var bmNotify;
var fnLarge;
var fnNormal;
function initialize() {
WatchFace.initialize();
bmBluetooth = WatchUi.loadResource(Rez.Drawables.BluetoothIcon);
bmNotify = WatchUi.loadResource(Rez.Drawables.NotifyIcon);
fnLarge = Graphics.FONT_NUMBER_MEDIUM;
fnNormal = Graphics.FONT_LARGE;
}
// Load your resources here
function onLayout(dc) {
center = [dc.getWidth()/2, dc.getHeight()/2];
}
// 時分を描画する
function drawHM(dc, time) {
var timeString = Lang.format("$1$:$2$", [time.hour, time.min.format("%02d")]);
var wh = dc.getTextDimensions(timeString, fnLarge);
dc.drawText(center[0], center[1] - Graphics.getFontAscent(fnLarge),
fnLarge, timeString, Graphics.TEXT_JUSTIFY_CENTER);
}
// 曜日を描画する
function drawDayWeek(dc, timeM, timeS) {
var dayString = Lang.format("$1$/$2$ $3$", [timeS.month, timeS.day, timeM.day_of_week]);
dc.drawText(center[0], center[1]
- Graphics.getFontAscent(fnLarge)
- Graphics.getFontAscent(fnNormal),
fnNormal, dayString,
Graphics.TEXT_JUSTIFY_CENTER);
}
// バッテリーパーセンテージを表示
function drawBattery(dc) {
dc.drawText(center[0], dc.getHeight() - dc.getFontHeight(fnNormal),
fnNormal, System.getSystemStats().battery.format("%3.0f") + "%",
Graphics.TEXT_JUSTIFY_CENTER);
}
// 心拍数を求める
private function retrieveHeartrateText() {
var currentHeartrate = ActivityMonitor.getHeartRateHistory(1, true).next().heartRate;
if (currentHeartrate != ActivityMonitor.INVALID_HR_SAMPLE) {
return currentHeartrate.format("%d");
}
else {
return "---";
}
}
// 歩数・心拍数などの情報を表示
function drawInformation(dc) {
var info = ActivityMonitor.getInfo();
var valueString = info.stepGoal.format("%5d") + "/" + info.steps.format("%5d");
if(ActivityMonitor has :INVALID_HR_SAMPLE) {
valueString += "\n" + retrieveHeartrateText();
}
dc.drawText(center[0], center[1],
fnNormal, valueString,
Graphics.TEXT_JUSTIFY_CENTER);
}
// Bluetoothとイベント通知アイコンの表示
function drawNotify(dc) {
var info = System.getDeviceSettings();
var y = center[1] + dc.getFontHeight(fnNormal) * 1.5;
if (info.notificationCount > 0) {
// メールアイコンの表示
dc.drawBitmap(center[0] * 1.5, y - bmNotify.getHeight() / 2, bmNotify);
}
if (info.phoneConnected) {
// BLEアイコンの表示
var wh = [bmBluetooth.getWidth(), bmBluetooth.getHeight()];
var tl = [center[0] / 2 - wh[0], y - wh[1] / 2];
dc.setColor(Graphics.COLOR_DK_BLUE, Graphics.COLOR_BLACK);
dc.fillEllipse(tl[0] + 12, y, wh[0] / 2 - 1, wh[1] / 2 - 1);
dc.drawBitmap(tl[0], tl[1], bmBluetooth);
}
}
// Update the view
function onUpdate(dc) {
dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_BLACK);
dc.clear();
var now = Time.now();
var nowM = Time.Gregorian.info(now, Time.FORMAT_MEDIUM);
var nowS = Time.Gregorian.info(now, Time.FORMAT_SHORT);
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
drawHM(dc, nowS);
drawDayWeek(dc, nowM, nowS);
drawInformation(dc);
drawNotify(dc);
dc.setColor(Graphics.COLOR_GREEN, Graphics.COLOR_BLACK);
drawBattery(dc);
}
}
注意点
月の表示
ソース中では、
var nowS = Time.Gregorian.info(now, Time.FORMAT_SHORT);
をの結果を使っている。はじめTime.FORMAT_MEDIUMの結果を使っていたのだが、実機では、「1月」と「月」という文字まで表示されてしまっていた。シミューレーターでは「月」は入っていなかったのだが。
そのため、Time.FORMAT_SHORTの結果を使うようにした。
時の表示
時計の設定を12時間表示にしても、そのままではAM/PM等の表示はされなかった。
この表示は、ウォッチフェイスのプログラム中で、System::DeviceSettings::is24Hourの値を見て、自前で表示を切り替えないといけないようだった。
自分は、24時間表示のほうを使っているので、今回のプログラムではそのまま表示している。
フォント
Garminウォッチフェイス作成:フォントサイズで調べた結果、全機種で共通で使えるようないいフォントがなかったので、自前で作成するか、機種依存にしてしまうか悩むところがある。
今回、自分が持っている機種でのみ利用することにしているので、その機種に合わせてフォントの調整をした。
心拍数の表示
// 心拍数を求める
private function retrieveHeartrateText() {
var currentHeartrate = ActivityMonitor.getHeartRateHistory(1, true).next().heartRate;
if (currentHeartrate != ActivityMonitor.INVALID_HR_SAMPLE) {
return currentHeartrate.format("%d");
}
else {
return "---";
}
}
当初は、ActivityMonitor.getHeartRateHistory(null, false)
を使っていたのだが、いろいろなソースを参照した結果、(1,true)
が一番多かったので、そちらに合わせた。
また、時計を付けていない期間が長いと、取り出した値にINVALID_HR_SAMPLE(255)が設定されるので、その場合には無効な値として”—“を表示するようにした。
ビットマップの描画と色の設定
Bluetoothアイコンは、当初、青と白の色で作成した。
青色は、ドキュメントに書かれていた16色パレットの中の0x00AAFFを指定したのだが、実際に表示されたときには、0x00FFFFと白とのデザリング色になってしまった。
色に関して簡単に調べたのだが、この時点ではそれ以上調べることができず、結局青には0x00FFFFだと明るすぎたので0x0000FFを使うようにした。
SDKのバージョンにより利用可能な機能の制限
ForeAthlete 45の前は、Vivoactive HR Jを使っていた。そちらのウォッチフェイスでは、階段上下階数や秒単位での表示更新なども、どの機種でもできると思っていたのだが、SDKにより利用できる機能にかなりの制限があることが分かった。
ちなみに、どの機種がどのバージョンなのかは、ドキュメントをざっと見た限りではわからなかった。
プロジェクトのマニフェストからビルドターゲットを選び、実行の構成でターゲット機種を選択した時に表示されるターゲットSDKバージョンで判断するしかなかった。
上のVenuはVersion 3台が使えるようだ。
APIがどのバージョンで利用可能かどうかは、ヘルプに記載がある。
実行結果
最後に
とりあえず、自分用にはいい感じのものができたと思う。
作成に要した時間は、途中、フォントや色やビットマップ作成の方法に試行錯誤したが、実質3日ぐらいだろうか。
コメント