LoginSignup
8
8

More than 5 years have passed since last update.

Watch faceを描画する

Last updated at Posted at 2015-01-19

android developersの、Drawing Watch Facesを適当意訳 自分用メモ

Watch faceの描画

Wearプロジェクトの設定が終了し、watch face serviceを実装したクラスを作成したら、あなたのcustom watch faceの初期化処理及び描画をするコードを書きましょう。

Implement the Service and Callback Methods

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

Initialize Your Watch Face

システムがあなたの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にて、その説明がなされています。
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で説明しています。

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を参照して下さい。

Declare 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で説明しているように、毎秒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で説明しているように、custom timerを作成しましょう。

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

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

    invalidate();
}

Configure the System UI

watch faceが、system UI elementsを妨害してはいけません。これは、 Accommodate System UI Elementsで説明しています。もし、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(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を参照して下さい。

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クラスのインスタンス"と"watch faceで描画したい(クラスのインスタンスである)bounds"を引数としたEngine.onDraw()メソッドを呼び出します。boundsは、はめ込み範囲の原因となります。?例えば、円形のデバイスの下部(あご)です。このcanvasを利用して、以下のようにwatch faceを直接描画できます。

  1. onDraw()メソッドの初回の実施時においては、背景にフィットするように大きさの調整を行います。
  2. デバイスが現在ambient modeなのかinteractive modeなのかをチェックします。
  3. 必要なグラフィック処理を行います。
  4. canvasに、背景用のbitmapを描画します。
  5. Canvasクラスのメソッドを使用し、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を参照して下さい。

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

8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8