[1/3](http://qiita.com/eoppp/items/e6f2a0783810026e4d56)からの続きです。
#Serviceの実装
##リソースファイルの追加
画像を追加します。
- watchfaceの背景画像と、プレビュー用の画像を追加します。
- とりあえず、背景画像として以下の様な320*320くらいの黒で塗りつぶしたファイルを用意しましょう。ファイル名は、ここでは"bg.xxx"とします。(xxxはpngやjpg等、画像形式にあわせて入力して下さい)
- そのファイルを、wear→res→drawableに入れます。画像をコピーして、drawableの上でペーストすると、入ります。
- 次に、プレビュー用の画像を追加します。
- プレビュー用の画像は、円形のwearableと四角形のwearable用に2種類用意する必要があります。
- 中身は同じでも問題ないのですが、とりあえず以下のような画像ファイルを用意しました。
- 背景画像と同様に、drawableに入れます。ファイル名は、それぞれ"preview.xxx"、"preview_circular.xxx"とします。
##コードの記述
1/3で作成した、AnalogWatchFaceServiceに、以下を記述しました。とりあえず動作確認用に使用するだけなので、コピペしてしまってください。なお、このコードは、SDKのサンプルをそのまま使用しています。(android-sdk/samples/android-21/wearable/WatchFace)
public class AnalogWatchFaceService extends CanvasWatchFaceService {
private static final String TAG = "AnalogWatchFaceService";
/**
* Update rate in milliseconds for interactive mode. We update once a second to advance the
* second hand.
*/
private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);
@Override
public Engine onCreateEngine() {
return new Engine();
}
private class Engine extends CanvasWatchFaceService.Engine {
static final int MSG_UPDATE_TIME = 0;
static final float TWO_PI = (float) Math.PI * 2f;
Paint mHourPaint;
Paint mMinutePaint;
Paint mSecondPaint;
Paint mTickPaint;
boolean mMute;
Calendar mCalendar;
/**
* 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:
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "updating 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;
}
}
};
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mCalendar.setTimeZone(TimeZone.getDefault());
invalidate();
}
};
boolean mRegisteredTimeZoneReceiver = false;
/**
* Whether the display supports fewer bits for each color in ambient mode. When true, we
* disable anti-aliasing in ambient mode.
*/
boolean mLowBitAmbient;
Bitmap mBackgroundBitmap;
Bitmap mBackgroundScaledBitmap;
@Override
public void onCreate(SurfaceHolder holder) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onCreate");
}
super.onCreate(holder);
setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
.setShowSystemUiTime(false)
.build());
Resources resources = AnalogWatchFaceService.this.getResources();
Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg, null /* theme */);
mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
mHourPaint = new Paint();
mHourPaint.setARGB(255, 200, 200, 200);
mHourPaint.setStrokeWidth(5.f);
mHourPaint.setAntiAlias(true);
mHourPaint.setStrokeCap(Paint.Cap.ROUND);
mMinutePaint = new Paint();
mMinutePaint.setARGB(255, 200, 200, 200);
mMinutePaint.setStrokeWidth(3.f);
mMinutePaint.setAntiAlias(true);
mMinutePaint.setStrokeCap(Paint.Cap.ROUND);
mSecondPaint = new Paint();
mSecondPaint.setARGB(255, 255, 0, 0);
mSecondPaint.setStrokeWidth(2.f);
mSecondPaint.setAntiAlias(true);
mSecondPaint.setStrokeCap(Paint.Cap.ROUND);
mTickPaint = new Paint();
mTickPaint.setARGB(100, 255, 255, 255);
mTickPaint.setStrokeWidth(2.f);
mTickPaint.setAntiAlias(true);
mCalendar = Calendar.getInstance();
}
@Override
public void onDestroy() {
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
super.onDestroy();
}
@Override
public void onPropertiesChanged(Bundle properties) {
super.onPropertiesChanged(properties);
mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onPropertiesChanged: low-bit ambient = " + mLowBitAmbient);
}
}
@Override
public void onTimeTick() {
super.onTimeTick();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());
}
invalidate();
}
@Override
public void onAmbientModeChanged(boolean inAmbientMode) {
super.onAmbientModeChanged(inAmbientMode);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
}
if (mLowBitAmbient) {
boolean antiAlias = !inAmbientMode;
mHourPaint.setAntiAlias(antiAlias);
mMinutePaint.setAntiAlias(antiAlias);
mSecondPaint.setAntiAlias(antiAlias);
mTickPaint.setAntiAlias(antiAlias);
}
invalidate();
// Whether the timer should be running depends on whether we're in ambient mode (as well
// as whether we're visible), so we may need to start or stop the timer.
updateTimer();
}
@Override
public void onInterruptionFilterChanged(int interruptionFilter) {
super.onInterruptionFilterChanged(interruptionFilter);
boolean inMuteMode = (interruptionFilter == WatchFaceService.INTERRUPTION_FILTER_NONE);
if (mMute != inMuteMode) {
mMute = inMuteMode;
mHourPaint.setAlpha(inMuteMode ? 100 : 255);
mMinutePaint.setAlpha(inMuteMode ? 100 : 255);
mSecondPaint.setAlpha(inMuteMode ? 80 : 255);
invalidate();
}
}
@Override
public void onDraw(Canvas canvas, Rect bounds) {
mCalendar.setTimeInMillis(System.currentTimeMillis());
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;
// Draw the ticks.
float innerTickRadius = centerX - 10;
float outerTickRadius = centerX;
for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
float tickRot = tickIndex * TWO_PI / 12;
float innerX = (float) Math.sin(tickRot) * innerTickRadius;
float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
float outerX = (float) Math.sin(tickRot) * outerTickRadius;
float outerY = (float) -Math.cos(tickRot) * outerTickRadius;
canvas.drawLine(centerX + innerX, centerY + innerY,
centerX + outerX, centerY + outerY, mTickPaint);
}
float seconds =
mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f;
float secRot = seconds / 60f * TWO_PI;
float minutes = mCalendar.get(Calendar.MINUTE) + seconds / 60f;
float minRot = minutes / 60f * TWO_PI;
float hours = mCalendar.get(Calendar.HOUR) + minutes / 60f;
float hrRot = hours / 12f * TWO_PI;
float secLength = centerX - 20;
float minLength = centerX - 40;
float hrLength = centerX - 80;
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);
}
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);
}
@Override
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onVisibilityChanged: " + visible);
}
if (visible) {
registerReceiver();
// Update time zone in case it changed while we weren't visible.
mCalendar.setTimeZone(TimeZone.getDefault());
} else {
unregisterReceiver();
}
// Whether the timer should be running depends on whether we're visible (as well as
// whether we're in ambient mode), so we may need to start or stop the timer.
updateTimer();
}
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);
}
/**
* Starts the {@link #mUpdateTimeHandler} timer if it should be running and isn't currently
* or stops it if it shouldn't be running but currently is.
*/
private void updateTimer() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "updateTimer");
}
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
if (shouldTimerBeRunning()) {
mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
}
}
/**
* Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer should
* only run when we're visible and in interactive mode.
*/
private boolean shouldTimerBeRunning() {
return isVisible() && !isInAmbientMode();
}
}
}
#実行
ここまででエラーがでていなければ、ひとまずプログラムは完成です。
早速、エミュレーター及び実機で実行してみましょう。
##エミュレーターでの実行
###エミュレーターの作成及び起動
- まず、エミュレーターを作成します。
- Tools→Android→AVD Managerを選択し、AVD Managerを起動します。
- Create Virtual Device...を選択します。
- 左側の"Category"から、"Wear"を選択します。
- 右側の、Android Wear SquareまたはAndroid Wear Roundを選択します。
- System Imageは、とりあえずLolipop, API Level21, x86のものを選択しておけばいいと思います。
- "Use Host GPU"には、一応チェックをつけておき、Finishで完成です。
- 完成したら、Actionsから再生ボタンを押して、エミュレーターを起動しましょう。
###Wearableの実行
- まず、Run→Edit Configurations...を選択します。
- 左側から、wearを選択します。(現在は、赤でバツが付いていると思います)
- 右側の、Activityの部分で、Do not launch Activity を選択し、OKとします。
- 以下から、wearを選択します。
- Run→Run wearを選択します。
- Choose a running Deviceで、先ほど作成したエミュレーターを選択します。
- 画面下部の、4:Runを選択すると、現在の状況が表示されます。こちらの最終行に、Successと表示されるまで待ちます。
- エミュレーター上で、時計が表示されている画面で、クリック長押しをすると、watch face選択画面に移行します。その中に、My Applicationという名前で、先ほど作成したプレビュー画像があると思います。
- そちらを選択すると、Watch faceが変更されます。
##実機での実行
以下が詳しいです。接続ができたら、エミュレーターと同様に実行します。
#カスタマイズ等
3/3に続きます。
#参考にしたサイト等
美女時計を作ろう!Android Wear開発入門 30分で作る盤面アプリ
Building a Watch Face Service(Android developers)