[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のエミュレータをペアリングすることもできるので、実機を持っていないけれどとりあえず開発したい!という方はエミュレータでも大丈夫です。

Screenshot_20161220-041008.png

Android WearのBluetooth経由のデバッグ方法

既にAndroid Wearアプリを開発したことがある方はここは飛ばしていただいて大丈夫です。

Android Wear側の準備

  1. Android Wearの設定からビルド番号を7回タップして「開発者向けオプション」を有効にする
    IMG_20161219_005341.jpg
  2. 開発者向けオプションから「ADBデバッグ」を有効にする
    IMG_20161219_011235.jpg
  3. 「Bluetooth経由でデバッグ」を有効にする
    IMG_20161219_011416.jpg

Android端末側の準備

  1. Android端末「開発者向けオプション」を有効にする

  2. 「USBデバッグ」を有効にする

  3. Android端末をUSBでPCに繋ぐ

  4. Android Wearアプリを開く

  5. 右上の設定ボタンを押す
    SO-02G23.5.B.0.303administrator12192016004849.png

  6. 一番下にスクロールして、デバッグする端末を選ぶ
    SO-02G23.5.B.0.303administrator12192016004925.png

  7. 「Bluetooth経由のデバッグ」をONにする
    SO-02G23.5.B.0.303administrator12192016004934.png

PCからAndroidとAndroid Wearを繋ぐ

  1. adb devicesでAndroid端末がUSB接続されていることを確認
    スクリーンショット 2016-12-19 0.59.58.png

  2. 以下のコマンドを実行し、Android Wearに接続する

    adb forward tcp:5555 localabstract:/adb-hub; adb connect localhost:5555
    

    結果はこんな感じになればOK
    スクリーンショット 2016-12-19 1.02.24.png

  3. Android Wear側で接続を許可する
    IMG_20161219_011625.jpg

  4. こんな感じで接続されているデバイスが増えていればOK
    スクリーンショット 2016-12-19 1.34.39.png

Watch Faceの作り方

ここから本題です。
今回は普通のデジタル時計の表示と、アンビエントモード(スリープモード)に変わった時に背景色が変わるような最低限の機能を持ったWatch Faceを作成してみます。
まずはいつも通りプロジェクトを作成します。

プロジェクトの作成

  1. プロジェクト名の設定
    スクリーンショット 2016-12-19 1.45.24.png

  2. Wearにチェックをいれて次へ
    スクリーンショット 2016-12-19 1.45.42.png

  3. 今回はWearアプリを作るのでAdd No Activityを選択して次へ
    スクリーンショット 2016-12-19 1.45.51.png

  4. Watch Faceを選びたいところですが、minSdkVersionが21以上でないとこの項目が選択できないのでAdd No Activityを選択して次へ
    スクリーンショット 2016-12-19 1.47.19.png

  5. あとはビルドを待ちます

WatchFaceServiceの作成

WatchFaceを作るためには、CanvasWatchFaceServiceを継承したServiceクラスを作成しなければならないので、まずはそれを作成します。
スクリーンショット 2016-12-19 1.58.56.png

中身はこんな感じで作っておきます。
この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 OptionsLaunchの部分をNothingに変えて、Default Activityを起動しないようにします。

スクリーンショット 2016-12-20 2.58.46.png

それができたら起動するModuleをwearに変えます。
スクリーンショット 2016-12-20 3.03.15.png

あとは、いつものように ボタンを押して、接続されているAndroid Wearデバイスを選択して起動します。
スクリーンショット 2016-12-20 3.02.50.png

起動後

インストールされたとき

インストールされるとこんな感じの通知が出てきます。

device-2016-12-19-032327.png

アンビエントモードOFF

盤面を触るとこうなります。

device-2016-12-20-030951.png

アンビエントモードON

何も触っていない状態だとこんな感じです。
輝度が無いので黒とグレーでは画像だとすごく伝わりにくいですが、実際に実機のAndroid Wearで起動してみると結構違います。

device-2016-12-20-031052.png

Watch Face選択画面

盤面を長押しした時に出てくるWatch Face選択画面では、このように出てきます。
ここにプレビューとして表示されている画像は、先ほどAndroidManifest.xmlで設定したpreview_circularです。
良い感じですね!

device-2016-12-20-024848.png

Android端末のAndroid WearアプリからのWatch Face選択画面

Android Wearアプリからも同じようにWatch Faceとして表示されるようになります。

device-2016-12-20-024918.png

おわりに

今回作成したコードは以下の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



コメント

人気の投稿

[Qiita] Google Playのクローズドベータ版テストでメールアドレスを指定して公開する

【Linux】Linuxでディレクトリ毎にzip圧縮する

[Qiita] Androidの実機でPCのlocalhostに接続したり、Webページの要素を検証する