[Qiita] Android WearのWatch Faceの作り方
※この記事は以前Qiitaに投稿されていた古い記事です
この記事は Androidその2 Advent Calendar 2016 の21日目の記事です。
はじめに
以前Androidの勉強会で、個人的に思うAndroid Wearのつらい話をしましたが、その少し後にこんなニュースがありました。
モトローラ、近く新型スマートウォッチをリリースする計画はなし–The Verge
http://japan.cnet.com/news/service/35093171/
ますますAndroid Wearのこの先が心配になってきましたね。
というわけで、そんなニュースは見なかったことにして、今回はAndroid WearのWatch Faceの作り方について書きます。
Watch Faceとは
Android Wearでは時計の盤面を変更することができます。
その時計の盤面というのがWatch Faceと呼ばれるものです。
Google PlayでWatch Face
と検索すると、既にたくさんのWatch Faceアプリが公開されているので今さら自分で作ろうと思う人はそんなにいないかもしれませんが、Watch Faceは気になってたけど何から手をつけていいかわからない!という方や、自分オリジナルのWatch Faceを作ってみたい!という方への作るきっかけになれば幸いです。
今回利用する端末
- Android
- Xperia Z3 Compact
- Android Wear
- Moto 360
今回は詳しくは紹介しませんが、Android Wearの実機を持っていなくても、Android端末の実機とAndroid Wearのエミュレータをペアリングすることもできるので、実機を持っていないけれどとりあえず開発したい!という方はエミュレータでも大丈夫です。
Android WearのBluetooth経由のデバッグ方法
既にAndroid Wearアプリを開発したことがある方はここは飛ばしていただいて大丈夫です。
Android Wear側の準備
- Android Wearの設定からビルド番号を7回タップして「開発者向けオプション」を有効にする
- 開発者向けオプションから「ADBデバッグ」を有効にする
- 「Bluetooth経由でデバッグ」を有効にする
Android端末側の準備
-
Android端末「開発者向けオプション」を有効にする
-
「USBデバッグ」を有効にする
-
Android端末をUSBでPCに繋ぐ
-
右上の設定ボタンを押す
-
一番下にスクロールして、デバッグする端末を選ぶ
-
「Bluetooth経由のデバッグ」をONにする
PCからAndroidとAndroid Wearを繋ぐ
-
adb devices
でAndroid端末がUSB接続されていることを確認
-
以下のコマンドを実行し、Android Wearに接続する
adb forward tcp:5555 localabstract:/adb-hub; adb connect localhost:5555
結果はこんな感じになればOK
-
Android Wear側で接続を許可する
-
こんな感じで接続されているデバイスが増えていればOK
Watch Faceの作り方
ここから本題です。
今回は普通のデジタル時計の表示と、アンビエントモード(スリープモード)に変わった時に背景色が変わるような最低限の機能を持ったWatch Faceを作成してみます。
まずはいつも通りプロジェクトを作成します。
プロジェクトの作成
-
プロジェクト名の設定
-
Wearにチェックをいれて次へ
-
今回はWearアプリを作るので
Add No Activity
を選択して次へ
-
Watch Face
を選びたいところですが、minSdkVersionが21以上でないとこの項目が選択できないのでAdd No Activity
を選択して次へ
-
あとはビルドを待ちます
WatchFaceServiceの作成
WatchFaceを作るためには、CanvasWatchFaceServiceを継承したServiceクラスを作成しなければならないので、まずはそれを作成します。
中身はこんな感じで作っておきます。
このCanvasWatchFaceService.Engine
を継承したクラスが重要になってくるので、一つずつ説明していきます。
public class DigitalWatchFaceService extends CanvasWatchFaceService {
@Override
public Engine onCreateEngine() {
return new Engine();
}
private class Engine extends CanvasWatchFaceService.Engine {
@Override
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
}
@Override
public void onAmbientModeChanged(boolean inAmbientMode) {
super.onAmbientModeChanged(inAmbientMode);
}
@Override
public void onTimeTick() {
super.onTimeTick();
}
@Override
public void onDraw(Canvas canvas, Rect bounds) {
super.onDraw(canvas, bounds);
}
}
}
Watch Faceの初期化
WatchFaceを描画するために必要なPaintや時間を取得するためのCalendarなどの初期化処理はonCreateの中で行います。
private class Engine extends CanvasWatchFaceService.Engine {
// 時間を取得するためのCalendar
Calendar mCalendar;
// 時計の背景色
int mBackgroundColor = Color.BLACK;
// 時間を描画するためのPaint
Paint mTimePaint;
// 日付を描画するためのPaint
Paint mDatePaint;
// 時間の位置を保持するためのPoint
Point mTimePosition;
// 日付の位置を保持するためのPoint
Point mDatePosition;
@Override
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
// 時間の文字色やサイズをセット
mTimePaint = new Paint();
mTimePaint.setColor(Color.WHITE);
mTimePaint.setTextSize(75);
mTimePaint.setAntiAlias(true);
// 日付の文字色やサイズをセット
mDatePaint = new Paint();
mDatePaint.setColor(Color.WHITE);
mDatePaint.setTextSize(20);
mDatePaint.setAntiAlias(true);
// Calendarを初期化
mCalendar = Calendar.getInstance();
// 位置を初期化
mTimePosition = new Point();
mDatePosition = new Point();
}
}
アンビエントモード
時計の盤面をタップしてからしばらく待つと暗くなりますが、その暗くなった状態をアンビエントモードと言います。
そのアンビエントモードは、onAmbientModeChanged
をOverrideすることで検知することができます。
引数に渡ってくる変数がtrueならON、falseならOFFです。
今回は単純にアンビエントモードがONであれば黒の背景色、OFFならグレーにするようにしてみます。
invalidateメソッドで再描画することにより、背景色の変更を反映しています。
@Override
public void onAmbientModeChanged(boolean inAmbientMode) {
super.onAmbientModeChanged(inAmbientMode);
mBackgroundColor = inAmbientMode ? Color.BLACK : Color.DKGRAY;
// 再描画
invalidate();
}
時間が変わった時の処理
時間が変わったときにはonTimeTick
というメソッドが呼ばれます。
時間が変わると言っても色々あると思いますが、具体的には以下の条件で呼ばれます。
- アンビエントモードとインタラクティブモードの両方で少なくとも1分に1回
- 日付または時刻が変わった時
- タイムゾーンが変わった時
1秒に1回呼びたい!といった場合は自分でタイマーを実装する必要がありますが、今回は分までしか表示しないため、onTimerTick
が呼ばれたら時計の盤面が再描画されるようにinvalidate
メソッドを呼んでおきます。
@Override
public void onTimeTick() {
super.onTimeTick();
// 再描画
invalidate();
}
時計の描画
これが一番のメインの処理になります。
時計を描画するためには、onDraw
メソッドをOverrideして自分でcanvasに描いてあげる必要があります。
ずっと呼ばれ続けるわけではないですが、通常のAndroidアプリで独自でViewを作るときと同じように、ここにはあまり重たい処理は書かないほうが良いです。
今回は日付と時間を中央に並べて、アンビエントモードで背景色が変わるようにするため、そのための描画処理をここに書いていきます。
@Override
public void onDraw(Canvas canvas, Rect bounds) {
super.onDraw(canvas, bounds);
// 現在時刻をCalendarにセット
mCalendar.setTimeInMillis(System.currentTimeMillis());
// 時間
String s_time = DateUtils.formatDateTime(
DigitalWatchFaceService.this,
mCalendar.getTimeInMillis(),
DateUtils.FORMAT_SHOW_TIME
);
Rect s_time_bounds = new Rect();
mTimePaint.getTextBounds(s_time, 0, s_time.length(), s_time_bounds);
// 中央に配置するための座標を計算する
mTimePosition.set(canvas.getWidth() / 2 - s_time_bounds.width() / 2, canvas.getHeight() / 2);
// 日付
String s_date = DateUtils.formatDateTime(
DigitalWatchFaceService.this,
mCalendar.getTimeInMillis(),
DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY
);
// 時間のテキストの大きさを再取得
mTimePaint.getTextBounds(s_time, 0, s_time.length(), s_time_bounds);
Rect s_date_bounds = new Rect();
// 日付のテキストの大きさを取得
mDatePaint.getTextBounds(s_date, 0, s_date.length(), s_date_bounds);
// 時間の下に良い感じに配置する
mDatePosition.set(canvas.getWidth() / 2 - s_date_bounds.width() / 2, canvas.getHeight() / 2 + s_time_bounds.height() / 2 + (int) (10 / getResources().getDisplayMetrics().density));
// 背景色を描画
canvas.drawColor(mBackgroundColor);
// 時間を描画
canvas.drawText(s_time, mTimePosition.x, mTimePosition.y, mTimePaint);
// 日付を描画
canvas.drawText(s_date, mDatePosition.x, mDatePosition.y, mDatePaint);
}
AndroidManifest.xmlの設定
最後に、AndroidManifest.xmlを設定します。
WatchFaceを選ぶときに表示されるプレビュー画像などもここで設定します。
<service
android:name=".DigitalWatchFaceService"
android:label="WatchFaceSample"
android:allowEmbedded="true"
android:taskAffinity=""
android:permission="android.permission.BIND_WALLPAPER">
<!-- valueの中身は自分のパッケージ名に変える -->
<meta-data
android:name="com.google.android.wearable.watchface.companionConfigurationAction"
android:value="net.syarihu.android.wearable.watchface.CONFIG_DIGITAL"/>
<!-- ここで設定しているxmlは下に記載 -->
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/watch_face"/>
<!-- 四角いデバイスでプレビューとして表示される画像 -->
<meta-data
android:name="com.google.android.wearable.watchface.preview"
android:resource="@drawable/preview"/>
<!-- 丸型デバイスでプレビューとして表示される画像 -->
<meta-data
android:name="com.google.android.wearable.watchface.preview_circular"
android:resource="@drawable/preview_circular"/>
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService"/>
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE"/>
</intent-filter>
</service>
watch_face.xmlはこんな感じです。
<?xml version="1.0" encoding="UTF-8"?>
<wallpaper />
起動してみる
ここまでできれば、あとは起動するだけです。
設定画面などを用意していれば話は別ですが、今回作成したWatch Faceは通常のアプリとは違いActivityが存在しないため、Run configuration
を少し変える必要があります。
具体的には以下のように、Launch Options
のLaunch
の部分をNothingに変えて、Default Activityを起動しないようにします。
それができたら起動するModuleをwear
に変えます。
あとは、いつものように ▶ ボタンを押して、接続されているAndroid Wearデバイスを選択して起動します。
起動後
インストールされたとき
インストールされるとこんな感じの通知が出てきます。
アンビエントモードOFF
盤面を触るとこうなります。
アンビエントモードON
何も触っていない状態だとこんな感じです。
輝度が無いので黒とグレーでは画像だとすごく伝わりにくいですが、実際に実機のAndroid Wearで起動してみると結構違います。
Watch Face選択画面
盤面を長押しした時に出てくるWatch Face選択画面では、このように出てきます。
ここにプレビューとして表示されている画像は、先ほどAndroidManifest.xmlで設定したpreview_circular
です。
良い感じですね!
Android端末のAndroid WearアプリからのWatch Face選択画面
Android Wearアプリからも同じようにWatch Faceとして表示されるようになります。
おわりに
今回作成したコードは以下のGitHubのリポジトリに公開していますので、よろしければご覧ください。
syarihu/WatchFaceSample
https://github.com/syarihu/WatchFaceSample
今回作成したのはどこにでもあるようなデジタル時計ですが、これをきっかけに、ぜひ自分オリジナルのWatch Faceを作ってみてください!
参考
Creating Watch Faces | Android Developers
https://developer.android.com/training/wearables/watch-faces/index.html
コメント
コメントを投稿