Watch faceを描画する

android developersの、[Drawing Watch Faces](https://developer.android.com/training/wearables/watch-faces/drawing.html)を適当意訳　自分用メモ
#Watch faceの描画

Wearプロジェクトの設定が終了し、watch face serviceを実装したクラスを作成したら、あなたのcustom watch faceの初期化処理及び描画をするコードを書きましょう。
>[Implement the Service and Callback Methods](https://developer.android.com/training/wearables/watch-faces/service.html#CallbackMethods)

このレッスンでは、androidシステムが、どのようにwatch face serviceのmethodsを呼び出すかを解説します。このサンプルは、 *android-sdk/samples/android-21/wearable/WatchFace* ディレクトリにあります。そのサンプルでは、どのようなwatch faceにも適用される、service実装に係るたくさんの説明がなされており(初期化処理や、deviceの特徴等)、あなた独自のwatch faceのコードに再利用することもできます。

##[Initialize Your Watch Face](https://developer.android.com/training/wearables/watch-faces/drawing.html#Initialize)

システムがあなたのserviceをloadした時、watch faceに必要な殆どのresourceファイルを変数に割り当てたり、初期化処理を行ったりする必要があります。例えば、bitmap resourceのloadや、独自のアニメーションを走らせるためのtimer objectsを生成したり、描画方法を設定したり、その他処理を行います。通常は、これらの処理を一度だけ行い、後はその結果を再利用するやり方を取るでしょう。このやり方により、watch faceのパフォーマンスを向上し、コードメンテナンスがしやすくなるでしょう。

watch faceの初期化を行うには、以下のステップが必要となります。

1. 変数を宣言します。
2. Engineクラスにおける、onCreate()メソッドのwatch faceの各要素を初期化します。
3. Engineクラスにおける、onVisibilityChanged()メソッドのcustom timerを初期化します。

次のセクションで、これらのステップを詳細に説明します。

###変数を宣言する

システムがあなたのwatch face serviceをロードした際に初期化されるresourceは、watch face実行中はいつでも利用可能である必要があり、つまり、それらは"再利用することができる"ということです。そのためには、あなたが**WatchFaceService.Engine**を実装したEngineクラス内で、これらのresourceのための変数を宣言すればいいのです。

以下のような要素の変数を宣言しましょう。

####Graphic objects

殆どのwatch faceは、背景用に少なくとも1つのbitmap imageを保有しています。[Create an Implementation Strategy](http://developer.android.com/training/wearables/watch-faces/designing.html#ImplementationStrategy)にて、その説明がなされています。
watch faceに使用するdesign elements(時計の針や、その他)に使用するために、bitmap imageを追加することも出来ます。

####Periodic timer

システムは、1分毎にwatch faceに通知を送ります。しかし、watch faceの中には独自のtime intervalによるアニメーションを実装しているものもあります。
この場合、そのwatch faceの更新に要求される頻度で時を刻む、独自のtimerを作成する必要があります。

####Time zone change receiver

ユーザーは、旅行中にタイム・ゾーンを変更することがあり、システムはこのeventを通知します。watch face serviceの実装においては、broadcast receiverを登録する必要があり、そしてそれはタイム・ゾーンが変わり、適切に時間をupdateした際に通知を受け取ります。

WatchFaceサンプルにおける、**AnalogWatchFaceService.Engine**クラスでは、これらの変数を以下のように定義しています。custom timerは、**Handler**のインスタンスとして実装されており、スレッドのmessage queueを使用したdelayed messagesを投げ？、また処理します。サンプルのwatch faceでは、custom timerは1秒間に1回だけ時間を刻みます。timerが時間を刻んだとき、handlerは**invalidate()**メソッドを呼び、それからシステムは**onDraw()**メソッドを呼びwatch faceを再描画します。

```
private class Engine extends CanvasWatchFaceService.Engine {
    static final int MSG_UPDATE_TIME = 0;

    /* a time object */
    Time mTime;

    /* device features */
    boolean mLowBitAmbient;

    /* graphic objects */
    Bitmap mBackgroundBitmap;
    Bitmap mBackgroundScaledBitmap;
    Paint mHourPaint;
    Paint mMinutePaint;
    ...

    /* handler to update the time once a second in interactive mode */
    final Handler mUpdateTimeHandler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            switch (message.what) {
                case MSG_UPDATE_TIME:
                    invalidate();
                    if (shouldTimerBeRunning()) {
                        long timeMs = System.currentTimeMillis();
                        long delayMs = INTERACTIVE_UPDATE_RATE_MS
                                - (timeMs % INTERACTIVE_UPDATE_RATE_MS);
                        mUpdateTimeHandler
                            .sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
                    }
                    break;
            }
        }
    };

    /* receiver to update the time zone */
    final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            mTime.clear(intent.getStringExtra("time-zone"));
            mTime.setToNow();
        }
    };

    /* service methods (see other sections) */
    ...
}
```

####Initialize watch face elements

bitmap resource、paint styles、その他watch faceの再描画に必要な要素に係るメンバ変数を宣言し、システムがサービスをロードした時にそれらが初期化されるようにしましょう。初期化処理は1回だけにし、それらを再利用することで、パフォーマンスやバッテリー持ちが改善されます。

**Engine.onCreate()**メソッドでは、以下の要素を初期化します。

1. 背景画像をロードします。
2. graphic objectsを作成のため、styleやcolorを設定します。
3. timeを取得するためのオブジェクトを作成します。
4. system UIの設定をします。

サンプル内の、"AnalogWatchFaceService"クラスにある**Engine.onCreate()**メソッドでは、これらの要素を以下のように初期化しています。

```
@Override
public void onCreate(SurfaceHolder holder) {
    super.onCreate(holder);

    /* configure the system UI (see next section) */
    ...

    /* load the background image */
    Resources resources = AnalogWatchFaceService.this.getResources();
    Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
    mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();

    /* create graphic styles */
    mHourPaint = new Paint();
    mHourPaint.setARGB(255, 200, 200, 200);
    mHourPaint.setStrokeWidth(5.0f);
    mHourPaint.setAntiAlias(true);
    mHourPaint.setStrokeCap(Paint.Cap.ROUND);
    ...

    /* allocate an object to hold the time */
    mTime = new Time();
}
```

背景用のbitmapは、システムがwatch faceを初期化する際に1回だけloadされます。graphic stylesは、Paintクラスのインスタンスとして作成します。これらのstylesは後ほど、**Engine.onDraw()**メソッド内でwatch faceの要素を描画する際に使用します。詳細は、この後の[Drawing Your Watch Face](http://developer.android.com/training/wearables/watch-faces/drawing.html#Drawing)で説明しています。

####Initialize the custom timer

watch faceの開発者として、デバイスがinteractive modeの間は、必要な頻度で時間を刻むcustom timerを作成することにより、watch faceの更新頻度を決めることができます。この事を利用して、watch faceに独自のアニメーションやその他のエフェクトを作成することができます。ambient modeでは、timerを無効化し、CPUをスリープにするようにして、分が進んだときだけwatch faceが更新されるようにしましょう。詳しくは、[Optimizing Performance and Battery Life](http://developer.android.com/training/wearables/watch-faces/performance.html)を参照して下さい。

[Declare variables](http://developer.android.com/training/wearables/watch-faces/drawing.html#Variables)では、毎秒ごとに時間を刻むように定義された**AnalogWatchFaceService**クラスにおけるtimerの例を確認することができます。**Engine.onVisibilityChanged()**メソッドにおいて、以下の2つの状況に当てはまる場合に、custom timerが開始するようにします。

* watch faceが見られる(表示されている)
* デバイスが、interactive modeである

(つまり、interactive modeにおいてwatch faceが表示されている状態)

サンプルのtimerは、上記のほかはどのような状態においても走らないはずです?。なぜなら、このwatch faceは、バッテリー保護のため、ambient modeでは秒針を描画しないようにしているからです。**AnalogWatchFaceService**クラスは、以下のように、次回のtimer tickのスケジューリングを行います。

```
private void updateTimer() {
    mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
    if (shouldTimerBeRunning()) {
        mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
    }
}

private boolean shouldTimerBeRunning() {
    return isVisible() && !isInAmbientMode();
}
```

このcustom timerは、[Declare variables](http://developer.android.com/training/wearables/watch-faces/drawing.html#Variables)で説明しているように、毎秒1回時間を刻みます。

**Engine.onVisibilityChanged()**メソッドでは、以下のように、必要な場合timerを開始したり、タイム・ゾーンの変更情報を受け取るreceiverを登録します。

```
@Override
public void onVisibilityChanged(boolean visible) {
    super.onVisibilityChanged(visible);

    if (visible) {
        registerReceiver();

        // Update time zone in case it changed while we weren't visible.
        mTime.clear(TimeZone.getDefault().getID());
        mTime.setToNow();
    } else {
        unregisterReceiver();
    }

    // Whether the timer should be running depends on whether we're visible and
    // whether we're in ambient mode), so we may need to start or stop the timer
    updateTimer();
}
```

watch faceの画面が表示されているとき、**onVisibilityChanged()**メソッドはタイム・ゾーンの変更情報を受け取るreceiverを登録したり、機器がinteractive modeの時に独自のtimerを開始します。watch faceの画面が非表示のときは、このメソッドによりtimerは停止し、receiverの登録を解除します。**registerReceiver()**及び**unregisterReceiver()**メソッドは、以下のように実装されます。

```
private void registerReceiver() {
    if (mRegisteredTimeZoneReceiver) {
        return;
    }
    mRegisteredTimeZoneReceiver = true;
    IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
    AnalogWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
}

private void unregisterReceiver() {
    if (!mRegisteredTimeZoneReceiver) {
        return;
    }
    mRegisteredTimeZoneReceiver = false;
    AnalogWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
}
```

####Invalidate the canvas when the time changes

1分毎にシステムによって**Engine.onTimeTick()**メソッドが呼ばれます。ambient modeでは、1分毎にwatch faceを更新することで通常問題ありません。interactive modeにおいて、watch faceの更新頻度をより高くするためには、[Initialize the custom timer](http://developer.android.com/training/wearables/watch-faces/drawing.html#Timer)で説明しているように、custom timerを作成しましょう。

watch faceの実装においては、ほとんどの場合、時間が経過した際におけるwatch faceの再描画のために、それまでのcanvasを無効化する処理のみをしています。

```
@Override
public void onTimeTick() {
    super.onTimeTick();

    invalidate();
}
```

##[Configure the System UI](http://developer.android.com/training/wearables/watch-faces/drawing.html#SystemUI)

watch faceが、system UI elementsを妨害してはいけません。これは、[ Accommodate System UI Elements](http://developer.android.com/design/wear/watchfaces.html#SystemUI)で説明しています。もし、watch faceの背景画像が明るいものであったり、watch faceの表示(時間やその他の情報等)を画面の下部付近に表示するようなものであれば、notification cardsの大きさを調整したり、background protectionを有効にする必要があるかも知れません。

Android Wearでは、watch faceの起動中において、以下に示すようなsystem UIの形状を設定することが出来ます。

* 一番目のnotification cardについて、画面上どの程度見えるようにするか(画面のどの程度を占有するか)を決める
* watch faceに、systemによる時刻を表示する
* ambient mode時において、cardを表示するか隠す
* systemによるindicatorを、単色の背景で?カバーする
* systemによるindicatorの位置を決める

system UIのこれらの要素を設定する場合は、**WatchFaceStyle**のインスタンスを作成し、そのインスタンスを**Engine.setWatchFaceStyle()**メソッドに渡しましょう。

**AnalogWatchFaceService**クラスにおいては、system UIを以下のように設定しています。

```
@Override
public void onCreate(SurfaceHolder holder) {
    super.onCreate(holder);

    /* configure the system UI */
    setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)
            .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
            .setBackgroundVisibility(WatchFaceStyle
                                    .BACKGROUND_VISIBILITY_INTERRUPTIVE)
            .setShowSystemUiTime(false)
            .build());
    ...
}
```

上記コードでは、cardが1つの同じ高さになるようにし、cardがinterruptive notificationとして少しの間しか表示されないようにし、システム時間が表示されないようにしています。(このwatch faceでは独自の時間表示を実装しているため)

watch faceの実装において、system UIのスタイルを設定できます。例えば、ユーザーが白色の背景を選んだときは、システムによるindicatorの表示部分に、背景をつけることができます。

system UIに関する詳細については、[API reference](http://developer.android.com/shareables/training/wearable-support-docs.zip)(zipファイル)の**WatchFaceStyle**クラスを参照して下さい。

####Obtain Information About the Device Screen

システムが画面表示時の設定を決めるとき、**Engine.onPropertiesChanged()**メソッドを呼び出します。たとえば、"low-bit ambient modeを使用するかどうか"や、"スクリーンがburn-in protectionを必要としているかどうか"(スクリーンに有機ELを使用しているデバイス等)です。

以下のコードにおいて、これらの設定方法を示しています。

```
@Override
public void onPropertiesChanged(Bundle properties) {
    super.onPropertiesChanged(properties);
    mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
    mBurnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION,
            false);
}
```

watch faceを描画する際は、これらの設定を考慮しましょう。

low-bit ambient modeを使用するdeviceでは、ambient modeにおいて各色にあまりビットを割けないので、アンチエイリアス処理は無効としましょう。

burn-in protectionが必要な機器の場合、ambient modeでは白ピクセルの塊の使用は避けるようにし、スクリーン端から10ピクセル以内には、コンテンツを表示しないようにします。システムが、焼きつき防止のために、定期的にコンテンツを数ピクセルずらすためです。
low-bit ambient modeやburn-in protectionの詳細については、[Optimize for Special Screens](http://developer.android.com/design/wear/watchfaces.html#SpecialScreens)を参照して下さい。

####Respond to Changes Between Modes

ambient modeとinteractive mode間の移行の際、システムは**Engine.onAmbientModeChanged()**を呼びます。watch faceサービス実装においては、2つのモードの移行時における重要な調整を行うようにし、また**invalidate()**メソッドを呼び出し、watch faceを再描画するようにしましょう。

以下のコードでは、(Android SDKの**WatchFace**サンプル内にある)**AnalogWatchFaceService**クラスにおいて、どのようにこのメソッドを実装しているかを示しています。

```
@Override
public void onAmbientModeChanged(boolean inAmbientMode) {

    super.onAmbientModeChanged(inAmbientMode);

    if (mLowBitAmbient) {
        boolean antiAlias = !inAmbientMode;
        mHourPaint.setAntiAlias(antiAlias);
        mMinutePaint.setAntiAlias(antiAlias);
        mSecondPaint.setAntiAlias(antiAlias);
        mTickPaint.setAntiAlias(antiAlias);
    }
    invalidate();
    updateTimer();
}
```

この例では、いくつかのgraphic styleの調整を行い、またwatch faceの再描画ができるように、現在のcanvasを無効化しています。

This example makes adjustments to some graphic styles and invalidates the canvas so the system can redraw the watch face.

####Draw Your Watch Face

独自のwatch face描画をするため、システムは、"[Canvas](http://developer.android.com/reference/android/graphics/Canvas.html)クラスのインスタンス"と"watch faceで描画したい(クラスのインスタンスである)bounds"を引数とした**Engine.onDraw()**メソッドを呼び出します。boundsは、はめ込み範囲の原因となります。?例えば、円形のデバイスの下部(あご)です。このcanvasを利用して、以下のようにwatch faceを直接描画できます。

1. **onDraw()**メソッドの初回の実施時においては、背景にフィットするように大きさの調整を行います。
2. デバイスが現在ambient modeなのかinteractive modeなのかをチェックします。
3. 必要なグラフィック処理を行います。
4. canvasに、背景用のbitmapを描画します。
5. [Canvas](http://developer.android.com/reference/android/graphics/Canvas.html)クラスのメソッドを使用し、watch faceを描画を行います。

WatchFace sampleにおける**AnalogWatchFaceService**クラスでは、以下のように**onDraw()**メソッドを実装して、これらのステップに従った処理をしています。

```
@Override
public void onDraw(Canvas canvas, Rect bounds) {
    // Update the time
    mTime.setToNow();

    int width = bounds.width();
    int height = bounds.height();

    // Draw the background, scaled to fit.
    if (mBackgroundScaledBitmap == null
        || mBackgroundScaledBitmap.getWidth() != width
        || mBackgroundScaledBitmap.getHeight() != height) {
        mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap,
                                         width, height, true /* filter */);
    }
    canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null);

    // Find the center. Ignore the window insets so that, on round watches
    // with a "chin", the watch face is centered on the entire screen, not
    // just the usable portion.
    float centerX = width / 2f;
    float centerY = height / 2f;

    // Compute rotations and lengths for the clock hands.
    float secRot = mTime.second / 30f * (float) Math.PI;
    int minutes = mTime.minute;
    float minRot = minutes / 30f * (float) Math.PI;
    float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;

    float secLength = centerX - 20;
    float minLength = centerX - 40;
    float hrLength = centerX - 80;

    // Only draw the second hand in interactive mode.
    if (!isInAmbientMode()) {
        float secX = (float) Math.sin(secRot) * secLength;
        float secY = (float) -Math.cos(secRot) * secLength;
        canvas.drawLine(centerX, centerY, centerX + secX, centerY +
                        secY, mSecondPaint);
    }

    // Draw the minute and hour hands.
    float minX = (float) Math.sin(minRot) * minLength;
    float minY = (float) -Math.cos(minRot) * minLength;
    canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY,
                    mMinutePaint);
    float hrX = (float) Math.sin(hrRot) * hrLength;
    float hrY = (float) -Math.cos(hrRot) * hrLength;
    canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY,
                    mHourPaint);
}
```

このメソッドでは、現在の時間を基にして時計の針の場所を計算しており、そしてそれを**onCreate()**メソッドにて初期化したgraphic styleを使用して、背景用のbitmap上にそれを描画します。なお、秒針はinteractive modeでのみ描画され、ambient modeでは描画されません。

Canvasクラスのインスタンスの描画に係る追加情報については、[Canvas instance](http://developer.android.com/guide/topics/graphics/2d-graphics.html)を参照して下さい。

Android SDK内のWatchFace sampleには、**onDraw()**メソッドを実装する方法の例として、参照可能なwatch faceがあります。