2015年3月13日

Android

Android Wearで便利にYahoo!ニュースを届ける〜ウォッチフェイス開発〜

  • このエントリーをはてなブックマークに追加

はじめまして、アプリ開発室でAndroidアプリを開発している福島です。
先日、Yahoo!ニュースをAndroid Wearに対応させるため、ウォッチフェイスを作成しました。
今回はその開発手法について紹介したいと思います。

ウォッチフェイスとは

昨年12月に、ウォッチフェイスがGoogle Playからダウンロードできるようになったと発表され、それに合わせてウォッチフェイスAPIが公開されました。
ウォッチフェイスはスマートフォンで言うところの壁紙アプリのようなもので、端末の背景にセットして使用します。
一度壁紙としてセットしてしまえば、次回からアプリ起動の必要はありません。
ウォッチフェイスは手軽に見ることができて非常に便利ですが、指で操作ができないので、リストをスクロールするといったことはできません。
名前の通りなのですが、ウォッチフェイスは時計の顔なので、絶対に守りたいルールは時刻を大きく表示することです。
その上で、時刻に比べて強調しすぎない程度に付加情報を表示するのが好ましいです。

wear_news_round
※Yahoo!ニュースでは、画面上部に時刻を大きく表示し、下部にニュースタイトルを表示させている。

Yahoo!ニュースのウォッチフェイスとその仕組み

Yahoo!ニュースのウォッチフェイスはニュース記事が表示されますが、これはスマートフォン用Yahoo!ニュースアプリで取得したものを表示しています。
スマートフォン側で定期的にニュース記事を取得しているので、wear端末側でもすぐに最新のニュースを確認できます。
model
また、ニュースだけでなく天気情報も取得しています。
アプリ設定で地域を設定すると、その地域の天気によってウォッチフェイスの背景が変化します。
晴れのとき雨のとき

ウォッチフェイスの実装方法について

ウォッチフェイスはServiceの拡張クラスで実装します。
Canvasで描画するCanvasWatchFaceServiceと、OpenGLで描画するGles2WatchFaceServiceの2つがあります。
3Dや凝ったアニメーション等を使わない限りはCanvasで十分実装できると思います。

public class DigitalWatchFace extends CanvasWatchFaceService {

    @Override
    public Engine onCreateEngine() {
        return new Engine();
    }

    public class Engine extends CanvasWatchFaceService.Engine {


        /**
         * ここで、使用する画像を読み込んでおく
         */
        @Override
        public void onCreate(SurfaceHolder holder) {
            super.onCreate(holder);
        }


        /**
         * ウォッチフェイスの表示/非表示に合わせて描画処理を開始/停止する
         */
        @Override
        public void onVisibilityChanged(boolean visible) {
            super.onVisibilityChanged(visible);
        }


        /**
         * 時計にしばらく触れていないとAmbientModeに移行し、省電力モードに入る。
         * AmbientModeのときは、ウォッチフェイスを白黒表示にするのが好ましい。
         */
        @Override
        public void onAmbientModeChanged(boolean inAmbientMode) {
            super.onAmbientModeChanged(inAmbientMode);
        }


        /**
         * AmbientModeがONの時に、1分ごとに1回呼ばれる。
         * AmbientModeでないときは、呼ばれない。
         */
        @Override
        public void onTimeTick() {
            super.onTimeTick();
        }


        /**
         * このメソッド内で、時計端末が丸形か角形かを判断できる。
         * 丸形画面で表示がはみ出ないように注意する
         */
        @Override
        public void onApplyWindowInsets(WindowInsets insets) {
            super.onApplyWindowInsets(insets);
        }


        /**
         * ここでウォッチフェイスの描画を行う
         */
        @Override
        public void onDraw(Canvas canvas, Rect bounds) {
            super.onDraw(canvas, bounds);
        }
    }
}

バッテリーへの配慮

onDrawを呼べば呼ぶほど、描画処理が繰り返され、電池の消費量が多くなってしまいます。
秒針付きのアナログ時計なら毎秒1回、秒まで表示しないデジタル時計なら毎分1回onDrawを呼ぶだけで問題ありません。
アニメーションをさせたい場合のみ、それ以上の頻度でonDrawを呼ぶようにします。
実装はHandlerTimerなどを用いて繰り返し処理をします。
また、ウォッチフェイスが表示されていない場合は、画面の更新が必要ないので、タイマーを止めるようにします。

さらに、AmbientMode(画面をしばらく操作していないときの省電力モード)の場合、電力消費を抑えるために以下のルールを守って実装します。

  • 画面を白黒で描画する。
  • onTimeTick()が呼ばれたときのみ、時刻の表示を更新する。(秒針など、秒単位での更新はしない)

AmbientMode時のウォッチフェイス
※AmbientMode時は背景色が黒になり、天気アニメーションやニュース記事の動きが止まる。

/**
 * 時計の描画を更新するべきかどうかを返す
 * 時計が前面に出ていて、かつAmbientMode(モノクロ表示)でない場合は描画OK
 */
public boolean shouldBeTimerRunning() {
    return isVisible() && !isInAmbientMode();
}

/**
 * 時計を必要最低限の頻度で描画するためのハンドラ
 */
Handler mUpdateTimeHandler = new Handler() {

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);

        if (msg.what == MSG_UPDATE) { 

            // 更新
            invalidate();

            // 必要な頻度で再描画
            if (shouldBeTimerRunning()) { 
                mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE, INTERVAL_MS);
            }
        }
    }
};

タイムゾーンへの配慮

海外で使用する場合、時差が発生するので時刻が狂ってしまいます。
携帯電話のタイムゾーンが変更されたタイミングで、ウォッチフェイスの時刻も変更する必要があります。

private Time mTime;

/**
 * タイムゾーンの変更を受け取り、時刻を調整
 */
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
            //時刻を調整
            mTime.clear(intent.getStringExtra("time-zone"));
            mTime.setToNow();
        }

        //時刻を調整したら、再描画
        invalidate();
    }
};


/**
 * 画面が表示されているときだけ、BroadcastReceiverを登録
 */
@Override
public void onVisibilityChanged(boolean visible) {
    if(visible){
        registerReceiver();
    } else {
        unregisterReceiver(); 
    }
}

丸形端末への配慮

ディスプレイの形状(丸形 or 角形)にも注意しなければいけません。
角形ディスプレイを想定してデザインしたウォッチフェイスをそのまま丸形ディスプレイで表示すると、画面が見切れてしまうかもしれません。
見切れないようにデザインを微調整するか、丸形専用のデザインを用意する必要があります。

修正前修正後
※円形画面でも文字が見切れないよう、文字の位置を調整している。

端末が丸形かどうかは、WatchFaceService.EngineクラスのonApplyWindowInsets()で検出できます。

@Override
public void onApplyWindowInsets(WindowInsets insets) {
    super.onApplyWindowInsets(insets);
    // ニュースタイトルが画面中央に来るように、位置を計算
    if (insets.isRound()) {
        // 丸形の場合
    } else {
        // 角形の場合
    }
}

最後に

以上、ウォッチフェイスアプリを開発する際に気をつけたいことを紹介させていただきました。
時計の実装はある程度決まったパターンになると思うので、一度開発を経験すれば、二度目以降はスムースに開発を進められそうな印象を受けました。
また、ウォッチフェイスはデザインに力を入れることがとても重要だと感じました。
私もお気に入りのデザインのウォッチフェイスをいくつかインストールして、気分に合わせて使用しています。
今回開発したYahoo!ニュースのウォッチフェイスもデザインにこだわって作りました。
ぜひ使ってみてください!

Yahoo! JAPANでは情報技術を駆使して人々や社会の課題を一緒に解決していける方を募集しています。詳しくは採用情報をご覧ください。

  • このエントリーをはてなブックマークに追加