added watchface module
This commit is contained in:
43
wear/src/main/AndroidManifest.xml
Normal file
43
wear/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.android.sunshine.wear">
|
||||
|
||||
<uses-feature android:name="android.hardware.type.watch" />
|
||||
|
||||
<!-- Required to act as a custom watch face. -->
|
||||
<uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@android:style/Theme.DeviceDefault">
|
||||
<service
|
||||
android:name=".SunshineWatchFaceService"
|
||||
android:label="@string/watchface_name"
|
||||
android:permission="android.permission.BIND_WALLPAPER">
|
||||
<meta-data
|
||||
android:name="android.service.wallpaper"
|
||||
android:resource="@xml/watch_face" />
|
||||
<meta-data
|
||||
android:name="com.google.android.wearable.watchface.preview"
|
||||
android:resource="@drawable/preview_digital" />
|
||||
<meta-data
|
||||
android:name="com.google.android.wearable.watchface.preview_circular"
|
||||
android:resource="@drawable/preview_digital_circular" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.service.wallpaper.WallpaperService" />
|
||||
|
||||
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
||||
android:value="@integer/google_play_services_version" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.sunshine.wear;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.support.wearable.watchface.CanvasWatchFaceService;
|
||||
import android.support.wearable.watchface.WatchFaceStyle;
|
||||
import android.text.format.Time;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Digital watch face with seconds. In ambient mode, the seconds aren't displayed. On devices with
|
||||
* low-bit ambient mode, the text is drawn without anti-aliasing in ambient mode.
|
||||
*/
|
||||
public class SunshineWatchFaceService extends CanvasWatchFaceService {
|
||||
private static final Typeface NORMAL_TYPEFACE =
|
||||
Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
|
||||
|
||||
/**
|
||||
* Update rate in milliseconds for interactive mode. We update once a second since seconds are
|
||||
* displayed in interactive mode.
|
||||
*/
|
||||
private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);
|
||||
|
||||
/**
|
||||
* Handler message id for updating the time periodically in interactive mode.
|
||||
*/
|
||||
private static final int MSG_UPDATE_TIME = 0;
|
||||
|
||||
@Override
|
||||
public SunshineWatchFaceEngine onCreateEngine() {
|
||||
return new SunshineWatchFaceEngine();
|
||||
}
|
||||
|
||||
private static class EngineHandler extends Handler {
|
||||
private final WeakReference<SunshineWatchFaceEngine> mWeakReference;
|
||||
|
||||
public EngineHandler(SunshineWatchFaceEngine reference) {
|
||||
mWeakReference = new WeakReference<>(reference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
SunshineWatchFaceEngine engine = mWeakReference.get();
|
||||
if (engine != null) {
|
||||
switch (msg.what) {
|
||||
case MSG_UPDATE_TIME:
|
||||
engine.handleUpdateTimeMessage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SunshineWatchFaceEngine extends CanvasWatchFaceService.Engine {
|
||||
final Handler mUpdateTimeHandler = new EngineHandler(this);
|
||||
boolean mRegisteredTimeZoneReceiver = false;
|
||||
Paint mBackgroundPaint;
|
||||
Paint mTextPaint;
|
||||
boolean mAmbient;
|
||||
Time mTime;
|
||||
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
mTime.clear(intent.getStringExtra("time-zone"));
|
||||
mTime.setToNow();
|
||||
}
|
||||
};
|
||||
int mTapCount;
|
||||
|
||||
float mXOffset;
|
||||
float mYOffset;
|
||||
|
||||
/**
|
||||
* Whether the display supports fewer bits for each color in ambient mode. When true, we
|
||||
* disable anti-aliasing in ambient mode.
|
||||
*/
|
||||
boolean mLowBitAmbient;
|
||||
|
||||
@Override
|
||||
public void onCreate(SurfaceHolder holder) {
|
||||
super.onCreate(holder);
|
||||
|
||||
setWatchFaceStyle(new WatchFaceStyle.Builder(SunshineWatchFaceService.this)
|
||||
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
|
||||
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
|
||||
.setShowSystemUiTime(false)
|
||||
.setAcceptsTapEvents(true)
|
||||
.build());
|
||||
Resources resources = SunshineWatchFaceService.this.getResources();
|
||||
mYOffset = resources.getDimension(R.dimen.digital_y_offset);
|
||||
|
||||
mBackgroundPaint = new Paint();
|
||||
mBackgroundPaint.setColor(resources.getColor(R.color.background));
|
||||
|
||||
mTextPaint = new Paint();
|
||||
mTextPaint = createTextPaint(resources.getColor(R.color.digital_text));
|
||||
|
||||
mTime = new Time();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private Paint createTextPaint(int textColor) {
|
||||
Paint paint = new Paint();
|
||||
paint.setColor(textColor);
|
||||
paint.setTypeface(NORMAL_TYPEFACE);
|
||||
paint.setAntiAlias(true);
|
||||
return paint;
|
||||
}
|
||||
|
||||
@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 (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);
|
||||
SunshineWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
|
||||
}
|
||||
|
||||
private void unregisterReceiver() {
|
||||
if (!mRegisteredTimeZoneReceiver) {
|
||||
return;
|
||||
}
|
||||
mRegisteredTimeZoneReceiver = false;
|
||||
SunshineWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets) {
|
||||
super.onApplyWindowInsets(insets);
|
||||
|
||||
// Load resources that have alternate values for round watches.
|
||||
Resources resources = SunshineWatchFaceService.this.getResources();
|
||||
boolean isRound = insets.isRound();
|
||||
mXOffset = resources.getDimension(isRound
|
||||
? R.dimen.digital_x_offset_round : R.dimen.digital_x_offset);
|
||||
float textSize = resources.getDimension(isRound
|
||||
? R.dimen.digital_text_size_round : R.dimen.digital_text_size);
|
||||
|
||||
mTextPaint.setTextSize(textSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPropertiesChanged(Bundle properties) {
|
||||
super.onPropertiesChanged(properties);
|
||||
mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeTick() {
|
||||
super.onTimeTick();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAmbientModeChanged(boolean inAmbientMode) {
|
||||
super.onAmbientModeChanged(inAmbientMode);
|
||||
if (mAmbient != inAmbientMode) {
|
||||
mAmbient = inAmbientMode;
|
||||
if (mLowBitAmbient) {
|
||||
mTextPaint.setAntiAlias(!inAmbientMode);
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures tap event (and tap type) and toggles the background color if the user finishes
|
||||
* a tap.
|
||||
*/
|
||||
@Override
|
||||
public void onTapCommand(int tapType, int x, int y, long eventTime) {
|
||||
Resources resources = SunshineWatchFaceService.this.getResources();
|
||||
switch (tapType) {
|
||||
case TAP_TYPE_TOUCH:
|
||||
// The user has started touching the screen.
|
||||
break;
|
||||
case TAP_TYPE_TOUCH_CANCEL:
|
||||
// The user has started a different gesture or otherwise cancelled the tap.
|
||||
break;
|
||||
case TAP_TYPE_TAP:
|
||||
// The user has completed the tap gesture.
|
||||
mTapCount++;
|
||||
mBackgroundPaint.setColor(resources.getColor(mTapCount % 2 == 0 ?
|
||||
R.color.background : R.color.background2));
|
||||
break;
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas, Rect bounds) {
|
||||
// Draw the background.
|
||||
if (isInAmbientMode()) {
|
||||
canvas.drawColor(Color.BLACK);
|
||||
} else {
|
||||
canvas.drawRect(0, 0, bounds.width(), bounds.height(), mBackgroundPaint);
|
||||
}
|
||||
|
||||
// Draw H:MM in ambient mode or H:MM:SS in interactive mode.
|
||||
mTime.setToNow();
|
||||
String text = mAmbient
|
||||
? String.format("%d:%02d", mTime.hour, mTime.minute)
|
||||
: String.format("%d:%02d:%02d", mTime.hour, mTime.minute, mTime.second);
|
||||
canvas.drawText(text, mXOffset, mYOffset, mTextPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle updating the time periodically in interactive mode.
|
||||
*/
|
||||
private void handleUpdateTimeMessage() {
|
||||
invalidate();
|
||||
if (shouldTimerBeRunning()) {
|
||||
long timeMs = System.currentTimeMillis();
|
||||
long delayMs = INTERACTIVE_UPDATE_RATE_MS
|
||||
- (timeMs % INTERACTIVE_UPDATE_RATE_MS);
|
||||
mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
wear/src/main/res/drawable-nodpi/preview_digital.png
Normal file
BIN
wear/src/main/res/drawable-nodpi/preview_digital.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
BIN
wear/src/main/res/drawable-nodpi/preview_digital_circular.png
Normal file
BIN
wear/src/main/res/drawable-nodpi/preview_digital_circular.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
BIN
wear/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
wear/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
BIN
wear/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
wear/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
wear/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
wear/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
BIN
wear/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
wear/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
6
wear/src/main/res/values/colors.xml
Normal file
6
wear/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="background">#000000</color>
|
||||
<color name="background2">#000088</color>
|
||||
<color name="digital_text">#ffffff</color>
|
||||
</resources>
|
||||
8
wear/src/main/res/values/dimens.xml
Normal file
8
wear/src/main/res/values/dimens.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="digital_text_size">40dp</dimen>
|
||||
<dimen name="digital_text_size_round">45dp</dimen>
|
||||
<dimen name="digital_x_offset">15dp</dimen>
|
||||
<dimen name="digital_x_offset_round">25dp</dimen>
|
||||
<dimen name="digital_y_offset">90dp</dimen>
|
||||
</resources>
|
||||
4
wear/src/main/res/values/strings.xml
Normal file
4
wear/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<resources>
|
||||
<string name="app_name">Sunshine</string>
|
||||
<string name="watchface_name">Weather</string>
|
||||
</resources>
|
||||
2
wear/src/main/res/xml/watch_face.xml
Normal file
2
wear/src/main/res/xml/watch_face.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android" />
|
||||
Reference in New Issue
Block a user