android developersの、Drawing Watch Facesを適当意訳 自分用メモ
#Watch faceの描画
Wearプロジェクトの設定が終了し、watch face serviceを実装したクラスを作成したら、あなたのcustom watch faceの初期化処理及び描画をするコードを書きましょう。
このレッスンでは、androidシステムが、どのようにwatch face serviceのmethodsを呼び出すかを解説します。このサンプルは、 android-sdk/samples/android-21/wearable/WatchFace ディレクトリにあります。そのサンプルでは、どのようなwatch faceにも適用される、service実装に係るたくさんの説明がなされており(初期化処理や、deviceの特徴等)、あなた独自のwatch faceのコードに再利用することもできます。
システムがあなたのserviceをloadした時、watch faceに必要な殆どのresourceファイルを変数に割り当てたり、初期化処理を行ったりする必要があります。例えば、bitmap resourceのloadや、独自のアニメーションを走らせるためのtimer objectsを生成したり、描画方法を設定したり、その他処理を行います。通常は、これらの処理を一度だけ行い、後はその結果を再利用するやり方を取るでしょう。このやり方により、watch faceのパフォーマンスを向上し、コードメンテナンスがしやすくなるでしょう。
watch faceの初期化を行うには、以下のステップが必要となります。
- 変数を宣言します。
- Engineクラスにおける、onCreate()メソッドのwatch faceの各要素を初期化します。
- 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()**メソッドでは、以下の要素を初期化します。
- 背景画像をロードします。
- graphic objectsを作成のため、styleやcolorを設定します。
- timeを取得するためのオブジェクトを作成します。
- 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();
}
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を直接描画できます。
- **onDraw()**メソッドの初回の実施時においては、背景にフィットするように大きさの調整を行います。
- デバイスが現在ambient modeなのかinteractive modeなのかをチェックします。
- 必要なグラフィック処理を行います。
- canvasに、背景用のbitmapを描画します。
- 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があります。