情况:
我想要一个具有特定背景的表盘。用户应该能够从我定义的 8 个图像池中更改背景。
问题:
WearableListenerService 及其组件让我头疼,我不知道自己在做什么。我试图研究代码但无法获得代码的关系。然后我尝试使用谷歌在他们的演示中定义的代码,但它不能正常工作,因为 WatchFace 背景没有改变。
我已经尝试过的:
正如我已经提到的,我尝试使用 Googles Code。我也在互联网上搜索了教程,但他们也无法帮助我。我在下面添加了我当前的代码。
我真的不想看起来像一个懒惰的人,等待别人写我的代码。我真的尝试过,但我无法让它工作。
表盘
package net.premadegames.gravityfaces;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.Preference;
import android.support.wearable.watchface.CanvasWatchFaceService;
import android.support.wearable.watchface.WatchFaceStyle;
import android.text.format.Time;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.WindowInsets;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataEvent;
import com.google.android.gms.wearable.DataEventBuffer;
import com.google.android.gms.wearable.DataItem;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.DataMapItem;
import com.google.android.gms.wearable.Wearable;
import java.lang.ref.WeakReference;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
public class GravityFace extends CanvasWatchFaceService {
private static final String TAG = "DigitalWatchFaceService";
private static final Typeface NORMAL_TYPEFACE =
Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);
private static final int MSG_UPDATE_TIME = 0;
@Override
public Engine onCreateEngine() {
return new Engine();
}
private class Engine extends CanvasWatchFaceService.Engine implements DataApi.DataListener,
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
final Handler mUpdateTimeHandler = new EngineHandler(this);
GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(GravityFace.this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Wearable.API)
.build();
int faceID = DigitalWatchFaceUtil.DEFAULT_FACEID;
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mTime.clear(intent.getStringExtra("time-zone"));
mTime.setToNow();
}
};
boolean mRegisteredTimeZoneReceiver = false;
Drawable mBackgroundDrawable;
Bitmap mBackgroundBitmap;
Bitmap mBackgroundScaledBitmap;
Paint mTextPaint;
Resources resources;
boolean mAmbient;
Time mTime;
boolean mLowBitAmbient;
@Override
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
setWatchFaceStyle(new WatchFaceStyle.Builder(GravityFace.this)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
.setShowSystemUiTime(false)
.build());
resources = GravityFace.this.getResources();
mBackgroundDrawable = resources.getDrawable(R.drawable.face1);
mBackgroundBitmap = ((BitmapDrawable) mBackgroundDrawable).getBitmap();
mTextPaint = new Paint();
mTextPaint = createTextPaint(resources.getColor(R.color.digital_text));
mTextPaint.setShadowLayer(4.0f, 0.0f, 2f, Color.argb(60, 0, 0, 0));
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) {
mGoogleApiClient.connect();
registerReceiver();
// Update time zone in case it changed while we weren't visible.
mTime.clear(TimeZone.getDefault().getID());
mTime.setToNow();
} else {
unregisterReceiver();
if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
Wearable.DataApi.removeListener(mGoogleApiClient, this);
mGoogleApiClient.disconnect();
}
}
updateTimer();
}
private void registerReceiver() {
if (mRegisteredTimeZoneReceiver) {
return;
}
mRegisteredTimeZoneReceiver = true;
IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
GravityFace.this.registerReceiver(mTimeZoneReceiver, filter);
}
private void unregisterReceiver() {
if (!mRegisteredTimeZoneReceiver) {
return;
}
mRegisteredTimeZoneReceiver = false;
GravityFace.this.unregisterReceiver(mTimeZoneReceiver);
}
@Override
public void onApplyWindowInsets(WindowInsets insets) {
super.onApplyWindowInsets(insets);
// Load resources that have alternate values for round watches.
Resources resources = GravityFace.this.getResources();
boolean isRound = insets.isRound();
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();
}
private void updateBackgroundIfInteractive(int faceID) {
if (!isInAmbientMode()) {
if(faceID == 1) {
mBackgroundDrawable = resources.getDrawable(R.drawable.face1);
} else if(faceID == 2) {
mBackgroundDrawable = resources.getDrawable(R.drawable.face2);
} else if(faceID == 3) {
mBackgroundDrawable = resources.getDrawable(R.drawable.face3);
} else if(faceID == 4) {
mBackgroundDrawable = resources.getDrawable(R.drawable.face4);
} else if(faceID == 5) {
mBackgroundDrawable = resources.getDrawable(R.drawable.face5);
} else if(faceID == 6) {
mBackgroundDrawable = resources.getDrawable(R.drawable.face6);
} else if(faceID == 7) {
mBackgroundDrawable = resources.getDrawable(R.drawable.face7);
} else if(faceID == 8) {
mBackgroundDrawable = resources.getDrawable(R.drawable.face8);
} else {
mBackgroundDrawable = resources.getDrawable(R.drawable.face1);
}
mBackgroundBitmap = ((BitmapDrawable) mBackgroundDrawable).getBitmap();
}
}
private void setInteractiveFaceBackground(int faceID) {
updateBackgroundIfInteractive(faceID);
}
@Override
public void onDraw(Canvas canvas, Rect bounds) {
// Draw the background.
if (isInAmbientMode()) {
canvas.drawColor(Color.BLACK);
} else {
if (mBackgroundScaledBitmap == null || mBackgroundScaledBitmap.getWidth() != canvas.getWidth() || mBackgroundScaledBitmap.getHeight() != canvas.getHeight()) {
mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap, canvas.getWidth(), canvas.getHeight(), true /* filter */);
}
canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null);
}
// Draw H:MM in ambient mode or H:MM:SS in interactive mode.
mTime.setToNow();
String text = String.format("%d:%02d", mTime.hour, mTime.minute);
int width = bounds.width();
int height = bounds.height();
float centerX = width / 2f;
float centerY = height / 2f;
Rect boundsText = new Rect();
mTextPaint.getTextBounds(text, 0, text.length(), boundsText);
int text_height = boundsText.height();
int text_width = boundsText.width();
canvas.drawText(text, centerX - text_width / 2, centerY + text_height / 2, 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();
}
private void updateConfigDataItemAndUiOnStartup() {
DigitalWatchFaceUtil.fetchConfigDataMap(mGoogleApiClient,
new DigitalWatchFaceUtil.FetchConfigDataMapCallback() {
@Override
public void onConfigDataMapFetched(DataMap startupConfig) {
// If the DataItem hasn't been created yet or some keys are missing,
// use the default values.
setDefaultValuesForMissingConfigKeys(startupConfig);
DigitalWatchFaceUtil.putConfigDataItem(mGoogleApiClient, startupConfig);
updateUiForConfigDataMap(startupConfig);
}
}
);
}
private void setDefaultValuesForMissingConfigKeys(DataMap config) {
addIntKeyIfMissing(config, DigitalWatchFaceUtil.KEY_FACEID,
DigitalWatchFaceUtil.DEFAULT_FACEID);
}
private void addIntKeyIfMissing(DataMap config, String key, int color) {
if (!config.containsKey(key)) {
config.putInt(key, color);
}
}
@Override // DataApi.DataListener
public void onDataChanged(DataEventBuffer dataEvents) {
for (DataEvent dataEvent : dataEvents) {
if (dataEvent.getType() != DataEvent.TYPE_CHANGED) {
continue;
}
DataItem dataItem = dataEvent.getDataItem();
if (!dataItem.getUri().getPath().equals(
DigitalWatchFaceUtil.PATH_WITH_FEATURE)) {
continue;
}
DataMapItem dataMapItem = DataMapItem.fromDataItem(dataItem);
DataMap config = dataMapItem.getDataMap();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Config DataItem updated:" + config);
}
updateUiForConfigDataMap(config);
}
}
private void updateUiForConfigDataMap(final DataMap config) {
boolean uiUpdated = false;
for (String configKey : config.keySet()) {
if (!config.containsKey(configKey)) {
continue;
}
int color = config.getInt(configKey);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Found watch face config key: " + configKey + " -> "
+ Integer.toHexString(color));
}
if (updateUiForKey(faceID)) {
uiUpdated = true;
}
}
if (uiUpdated) {
invalidate();
}
}
/**
* Updates the color of a UI item according to the given {@code configKey}. Does nothing if
* {@code configKey} isn't recognized.
*
* @return whether UI has been updated
*/
private boolean updateUiForKey(int faceID) {
setInteractiveFaceBackground(faceID);
return true;
}
@Override // GoogleApiClient.ConnectionCallbacks
public void onConnected(Bundle connectionHint) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onConnected: " + connectionHint);
}
Wearable.DataApi.addListener(mGoogleApiClient, Engine.this);
updateConfigDataItemAndUiOnStartup();
}
@Override // GoogleApiClient.ConnectionCallbacks
public void onConnectionSuspended(int cause) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onConnectionSuspended: " + cause);
}
}
@Override // GoogleApiClient.OnConnectionFailedListener
public void onConnectionFailed(ConnectionResult result) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onConnectionFailed: " + result);
}
}
/**
* 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);
}
}
}
private static class EngineHandler extends Handler {
private final WeakReference<GravityFace.Engine> mWeakReference;
public EngineHandler(GravityFace.Engine reference) {
mWeakReference = new WeakReference<>(reference);
}
@Override
public void handleMessage(Message msg) {
GravityFace.Engine engine = mWeakReference.get();
if (engine != null) {
switch (msg.what) {
case MSG_UPDATE_TIME:
engine.handleUpdateTimeMessage();
break;
}
}
}
}
}
设置活动
package net.premadegames.gravityfaces;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.wearable.view.WatchViewStub;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.Wearable;
import java.util.Random;
public class SettingsActivity extends Activity {
private static final String TAG = "DigitalWatchFaceConfig";
private TextView mTextView;
private Button mButton;
Random r = new Random();
private GoogleApiClient mGoogleApiClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
@Override
public void onLayoutInflated(WatchViewStub stub) {
mTextView = (TextView) stub.findViewById(R.id.text);
mButton = (Button) stub.findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
int i1 = r.nextInt(7)+1;
updateConfigDataItem(i1);
finish();
}
});
}
});
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle connectionHint) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onConnected: " + connectionHint);
}
}
@Override
public void onConnectionSuspended(int cause) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onConnectionSuspended: " + cause);
}
}
})
.addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
@Override
public void onConnectionFailed(ConnectionResult result) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onConnectionFailed: " + result);
}
}
})
.addApi(Wearable.API)
.build();
}
@Override
protected void onStart() {
super.onStart();
mGoogleApiClient.connect();
}
@Override
protected void onStop() {
if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
mGoogleApiClient.disconnect();
}
super.onStop();
}
private void updateConfigDataItem(final int faceID) {
DataMap configKeysToOverwrite = new DataMap();
configKeysToOverwrite.putInt(DigitalWatchFaceUtil.KEY_FACEID, faceID);
DigitalWatchFaceUtil.overwriteKeysInConfigDataMap(mGoogleApiClient, configKeysToOverwrite);
}
}
数字表盘实用程序
package net.premadegames.gravityfaces;
import android.graphics.Color;
import android.net.Uri;
import android.util.Log;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataItem;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.DataMapItem;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.Wearable;
public final class DigitalWatchFaceUtil {
private static final String TAG = "DigitalWatchFaceUtil";
/**
* The {@link DataMap} key for {@link DigitalWatchFaceService} background color name.
* The color name must be a {@link String} recognized by {@link Color#parseColor}.
*/
public static final String KEY_FACEID = "FACEID";
/**
* The path for the {@link DataItem} containing {@link DigitalWatchFaceService} configuration.
*/
public static final String PATH_WITH_FEATURE = "/watch_face_config";
/**
* Name of the default interactive mode background color and the ambient mode background color.
*/
public static final Integer DEFAULT_FACEID = 1;
/**
* Callback interface to perform an action with the current config {@link DataMap} for
* {@link DigitalWatchFaceService}.
*/
public interface FetchConfigDataMapCallback {
/**
* Callback invoked with the current config {@link DataMap} for
* {@link DigitalWatchFaceService}.
*/
void onConfigDataMapFetched(DataMap config);
}
/**
* Asynchronously fetches the current config {@link DataMap} for {@link DigitalWatchFaceService}
* and passes it to the given callback.
* <p>
* If the current config {@link DataItem} doesn't exist, it isn't created and the callback
* receives an empty DataMap.
*/
public static void fetchConfigDataMap(final GoogleApiClient client,
final FetchConfigDataMapCallback callback) {
Wearable.NodeApi.getLocalNode(client).setResultCallback(
new ResultCallback<NodeApi.GetLocalNodeResult>() {
@Override
public void onResult(NodeApi.GetLocalNodeResult getLocalNodeResult) {
String localNode = getLocalNodeResult.getNode().getId();
Uri uri = new Uri.Builder()
.scheme("wear")
.path(DigitalWatchFaceUtil.PATH_WITH_FEATURE)
.authority(localNode)
.build();
Wearable.DataApi.getDataItem(client, uri)
.setResultCallback(new DataItemResultCallback(callback));
}
}
);
}
/**
* Overwrites (or sets, if not present) the keys in the current config {@link DataItem} with
* the ones appearing in the given {@link DataMap}. If the config DataItem doesn't exist,
* it's created.
* <p>
* It is allowed that only some of the keys used in the config DataItem appear in
* {@code configKeysToOverwrite}. The rest of the keys remains unmodified in this case.
*/
public static void overwriteKeysInConfigDataMap(final GoogleApiClient googleApiClient,
final DataMap configKeysToOverwrite) {
DigitalWatchFaceUtil.fetchConfigDataMap(googleApiClient,
new FetchConfigDataMapCallback() {
@Override
public void onConfigDataMapFetched(DataMap currentConfig) {
DataMap overwrittenConfig = new DataMap();
overwrittenConfig.putAll(currentConfig);
overwrittenConfig.putAll(configKeysToOverwrite);
DigitalWatchFaceUtil.putConfigDataItem(googleApiClient, overwrittenConfig);
}
}
);
}
/**
* Overwrites the current config {@link DataItem}'s {@link DataMap} with {@code newConfig}.
* If the config DataItem doesn't exist, it's created.
*/
public static void putConfigDataItem(GoogleApiClient googleApiClient, DataMap newConfig) {
PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(PATH_WITH_FEATURE);
DataMap configToPut = putDataMapRequest.getDataMap();
configToPut.putAll(newConfig);
Wearable.DataApi.putDataItem(googleApiClient, putDataMapRequest.asPutDataRequest())
.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
@Override
public void onResult(DataApi.DataItemResult dataItemResult) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "putDataItem result status: " + dataItemResult.getStatus());
}
}
});
}
private static class DataItemResultCallback implements ResultCallback<DataApi.DataItemResult> {
private final FetchConfigDataMapCallback mCallback;
public DataItemResultCallback(FetchConfigDataMapCallback callback) {
mCallback = callback;
}
@Override
public void onResult(DataApi.DataItemResult dataItemResult) {
if (dataItemResult.getStatus().isSuccess()) {
if (dataItemResult.getDataItem() != null) {
DataItem configDataItem = dataItemResult.getDataItem();
DataMapItem dataMapItem = DataMapItem.fromDataItem(configDataItem);
DataMap config = dataMapItem.getDataMap();
mCallback.onConfigDataMapFetched(config);
} else {
mCallback.onConfigDataMapFetched(new DataMap());
}
}
}
}
private DigitalWatchFaceUtil() { }
}