在該教程的第一部分,已經涉及到創(chuàng)建一個數字的Android Wear watchface的基礎知識。在這一部分中,將看到如何在Android Wear的移動應用中為其watchface添加一個面板設置。在學習完本文后,你將學會在你的移動設備上從Android Wear應用程序內部控制背景顏色、時間、日期和你的watchface的顏色。
為了建立腕表和移動設備之間的通信通道,你必須使用Google Play services中的Wearable Data Layer API。官方文件說明如下:
因為這些API是專為手持設備和可穿戴設備之間的通信而設計的,這是僅有的一些API,你應該使用它們設置這些設備之間的通信。例如,不要嘗試打開底層套接字創(chuàng)建一個通信信道。
在可穿戴數據層API的幫助下,你可以將將多種對象類型發(fā)送到你的設備上:
DataItems ——提供數據存儲和自動同步
[Messages]——主要用于遠程過程調用和單向請求
對于通信渠道,我們將使用DataApi 來發(fā)送從移動設備到數據層的DataItems ,而watchface將接受任何修改。
前面已經看到,我們將要使用的API是Google Play services的一部分,所以你必須在你的mobile/src/main/AndroidManifest.xml文件中 <application>
標簽之下指定一個meta-data實體:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.catinean.simpleandroidwatchface">
...
<application
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme">
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
</application>
</manifest>
下一步,在 mobile/src/main/java/your_package 中創(chuàng)建一個空的activity組件,這將作為你的watchface的activity組件設置。我們調用它的SimpleWatchFaceConfigurationActivity
。為了讓你的watchface認為該activity組件為設置activity組件,你必須在你的 mobile AndroidManifest.xml文件中指定一個<intent-filter>
:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.catinean.simpleandroidwatchface">
...
<application
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme">
<activity
android:name="com.catinean.simpleandroidwatchface.SimpleWatchFaceConfigurationActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="com.catinean.simpleandroidwatchface.CONFIG_DIGITAL" />
<category android:name="com.google.android.wearable.watchface.category.COMPANION_CONFIGURATION" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
</application>
</manifest>
正如你看到的,你必須為你的intent filter和類別指定一個自定義的 < action >。在我們的例子中,動作名稱是由封裝名稱和配置字符串:com.catinean.simpleandroidwatchface.CONFIG_DIGITAL組成的。
在穿戴模塊方面,你必須在 AndroidManifest.xml 文件中為你之前創(chuàng)建的<service>
實體增加一個額外的<meta-data>
字段:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.catinean.simpleandroidwatchface">
...
<application
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:theme="@android:style/Theme.DeviceDefault">
<service
android:name=".SimpleWatchFaceService"
android:label="@string/app_name"
android:permission="android.permission.BIND_WALLPAPER">
...
<meta-data
android:name="com.google.android.wearable.watchface.companionConfigurationAction"
android:value="com.catinean.simpleandroidwatchface.CONFIG_DIGITAL" />
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
</intent-filter>
</service>
</application>
</manifest>
讓我們看一下,元數據值
如何對應于activity組件的動作名稱
。
現在,activity組件是空的。為了用戶能夠配置watchface背景顏色、日期和時間的顏色,我們用兩個實體填充它。它的布局很簡單,形成一個帶有兩個元素的LinearLayout
(一個用于背景顏色,另一個用于日期和時間的顏色)。該 mobile/src/main/res/layout/activity_configuration.xml 文件將會是如下的形式:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
style="@style/MyActionBarStyle" />
<LinearLayout
android:layout_below="@+id/toolbar"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@drawable/delimiter"
android:showDividers="middle"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<RelativeLayout
android:id="@+id/configuration_background_colour"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/selector_preference_background"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:textColor="@android:color/black"
android:textSize="18sp"
android:text="@string/background_colour" />
<View
android:id="@+id/configuration_background_colour_preview"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentEnd="true" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/configuration_time_colour"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/selector_preference_background"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:textSize="18sp"
android:textColor="@android:color/black"
android:text="@string/date_and_time_colour" />
<View
android:id="@+id/configuration_date_and_time_colour_preview"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentEnd="true" />
</RelativeLayout>
</LinearLayout>
</RelativeLayout>
再回到activity組件,我們希望在對話框中顯示各元素顏色的名稱。為此,我們將創(chuàng)建一個簡單的ColourChooserDialog ,其將包含一個簡單的顏色列表,當用戶點擊一個activity組件的元素時,它將進行顯示。
該ColourChooserDialog
將會是這樣的:
package com.catinean.simpleandroidwatchface;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
public class ColourChooserDialog extends DialogFragment {
private static final String ARG_TITLE = "ARG_TITLE";
private Listener colourSelectedListener;
public static ColourChooserDialog newInstance(String dialogTitle) {
Bundle arguments = new Bundle();
arguments.putString(ARG_TITLE, dialogTitle);
ColourChooserDialog dialog = new ColourChooserDialog();
dialog.setArguments(arguments);
return dialog;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
colourSelectedListener = (Listener) activity;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
String title = getArguments().getString(ARG_TITLE);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(title)
.setItems(R.array.colors_array, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
String[] colours = getResources().getStringArray(R.array.colors_array);
colourSelectedListener.onColourSelected(colours[which], getTag());
}
});
return builder.create();
}
interface Listener {
void onColourSelected(String colour, String tag);
}
}
你可以看到,這是一個簡單的DialogFragment
,其通過一個字符串數組R.array.colors_array
顯示AlertDialog
列表(獲取更多關于對話框的一般信息,你可以在這里閱讀官方文檔)。
該字符串數組只是mobile/src/main/res/values/arrays.xml的內部資源:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="colors_array">
<item name="black">Black</item>
<item name="white">White</item>
<item name="red">Red</item>
<item name="green">Green</item>
<item name="cyan">Cyan</item>
<item name="magenta">Magenta</item>
</string-array>
</resources>
為了通知所選擇的顏色的活動,該對話框提供了一個接口。因為將有多個對話框的創(chuàng)建(一個用于背景顏色和一個用于日期和時間的顏色),而且必須要通過它們的tag
來區(qū)分。
SimpleWatchFaceConfigurationActivity
會是這樣的:
public class SimpleWatchFaceConfigurationActivity extends ActionBarActivity implements ColourChooserDialog.Listener {
private static final String TAG_BACKGROUND_COLOUR_CHOOSER = "background_chooser";
private static final String TAG_DATE_AND_TIME_COLOUR_CHOOSER = "date_time_chooser";
private View backgroundColourImagePreview;
private View dateAndTimeColourImagePreview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_configuration);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
findViewById(R.id.configuration_background_colour).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ColourChooserDialog.newInstance(getString(R.string.pick_background_colour))
.show(getFragmentManager(), TAG_BACKGROUND_COLOUR_CHOOSER);
}
});
findViewById(R.id.configuration_time_colour).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ColourChooserDialog.newInstance(getString(R.string.pick_date_time_colour))
.show(getFragmentManager(), TAG_DATE_AND_TIME_COLOUR_CHOOSER);
}
});
backgroundColourImagePreview = findViewById(R.id.configuration_background_colour_preview);
dateAndTimeColourImagePreview = findViewById(R.id.configuration_date_and_time_colour_preview);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onColourSelected(String colour, String tag) {
if (TAG_BACKGROUND_COLOUR_CHOOSER.equals(tag)) {
backgroundColourImagePreview.setBackgroundColor(Color.parseColor(colour));
} else {
dateAndTimeColourImagePreview.setBackgroundColor(Color.parseColor(colour));
}
}
}
如果現在你就要在穿戴
設備運行wear (穿戴)模塊
,然后是移動設備上的mobile(移動) 模塊,導航到 Android Wear
應用程序,你將能夠訪問activity組件的設置。
http://wiki.jikexueyuan.com/project/android-weekly/images/issue-146/8.1.gif" alt="alt text" />
現在,我們能夠捕捉到選定的顏色,現在該去了解我們是如何將它發(fā)送到API的數據層的。為了連接和發(fā)送數據,你將不得不使用GoogleApiClient
類。在activity組件中的onCreate()
,我們創(chuàng)建了對象,使用 onStart()
我們連接到了數據層,使用onStop()
斷開連接。我們增強的activity組件將會是這樣的:
public class SimpleWatchFaceConfigurationActivity extends ActionBarActivity implements ColourChooserDialog.Listener,
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
...
private static final String TAG = "SimpleWatchface";
private GoogleApiClient googleApiClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Wearable.API)
.build();
...
}
@Override
protected void onStart() {
super.onStart();
googleApiClient.connect();
}
...
@Override
public void onConnected(Bundle bundle) {
Log.d(TAG, "onConnected");
}
@Override
public void onConnectionSuspended(int i) {
Log.e(TAG, "onConnectionSuspended");
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.e(TAG, "onConnectionFailed");
}
@Override
protected void onStop() {
if (googleApiClient != null && googleApiClient.isConnected()) {
googleApiClient.disconnect();
}
super.onStop();
}
}
為了確保連接,我加了一些相應的回調日志。一個同步數據層由兩元素組成:
payload
: 實際要發(fā)送的數據。
path
:你要發(fā)送數據項的一個唯一的標識符(path必須以正斜杠開頭
,例如"/simple_watch_face_config")。
我們將在 PutDataMapRequest
的幫助下發(fā)送數據,這為我們提供了一個類似于行為的關鍵值。onColourSelected(String colour, String tag)
方法將會是這樣的:@Override
public void onColourSelected(String colour, String tag) {
PutDataMapRequest putDataMapReq = PutDataMapRequest.create("/simple_watch_face_config");
if (TAG_BACKGROUND_COLOUR_CHOOSER.equals(tag)) {
backgroundColourImagePreview.setBackgroundColor(Color.parseColor(colour));
putDataMapReq.getDataMap().putString("KEY_BACKGROUND_COLOUR", colour);
} else {
dateAndTimeColourImagePreview.setBackgroundColor(Color.parseColor(colour));
putDataMapReq.getDataMap().putString("KEY_DATE_TIME_COLOUR", colour);
}
PutDataRequest putDataReq = putDataMapReq.asPutDataRequest();
Wearable.DataApi.putDataItem(googleApiClient, putDataReq);
}
你會看到在指定的path 上如何創(chuàng)建一個PutDataRequest
,每次收到一個顏色,就用一個特定的key填充映射。最后,在Wearable.DataApi.putDataItem(googleApiClient, putDataReq)
方法的幫助下發(fā)送請求。
現在,我們能夠發(fā)送顏色,必須在可穿戴模型中對它們進行處理,具體是在先前創(chuàng)建的SimpleWatchFaceService
里處理。
回到可穿戴模型,我們必須處理SimpleWatchFaceService
中之前通過SimpleWatchFaceConfigurationActivity
發(fā)來的配置信息。
正如在配置activity組件中所做的那樣,為了與數據層API同步,必須首先通過一個GoogleApiClient
對象連接到它。當watchface是可見時,我們將開始連接到API;當watchface是不可見的時,將端口與API的連接。你的SimpleWatchFaceService
將會是這樣:
public class SimpleWatchFaceService extends CanvasWatchFaceService {
...
private class SimpleEngine extends CanvasWatchFaceService.Engine implements
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
...
private GoogleApiClient googleApiClient;
@Override
public void onCreate(SurfaceHolder holder) {
...
googleApiClient = new GoogleApiClient.Builder(SimpleWatchFaceService.this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
...
@Override
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
if (visible) {
...
googleApiClient.connect();
} else {
...
releaseGoogleApiClient();
}
...
}
private void releaseGoogleApiClient() {
if (googleApiClient != null && googleApiClient.isConnected()) {
googleApiClient.disconnect();
}
}
...
@Override
public void onConnected(Bundle bundle) {
Log.d(TAG, "connected GoogleAPI");
}
@Override
public void onConnectionSuspended(int i) {
Log.e(TAG, "suspended GoogleAPI");
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.e(TAG, "connectionFailed GoogleAPI");
}
@Override
public void onDestroy() {
...
releaseGoogleApiClient();
super.onDestroy();
}
}
}
我們在SimpleEngine
的 onCreate()
方法中創(chuàng)建了GoogleApiClient
對象,當watchface是可見的時進行連接,當watchface是不可見的時釋放客戶端。
下一步,當移動activity組件的背景顏色和日期、時間顏色的值改變以及每次watch face連接到數據層時,我們想要得到通知。為了實現這個功能,不得不使用:
DataApi.DataListener
——每次改變數據層的某些數據時,將會獲得通知。
ResultCallback
——每次googleApiClient 連接時,它將會通知我們。public class SimpleWatchFaceService extends CanvasWatchFaceService {
...
private class SimpleEngine extends CanvasWatchFaceService.Engine implements
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
...
private GoogleApiClient googleApiClient;
@Override
public void onCreate(SurfaceHolder holder) {
...
googleApiClient = new GoogleApiClient.Builder(SimpleWatchFaceService.this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
...
@Override
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
if (visible) {
...
googleApiClient.connect();
} else {
...
releaseGoogleApiClient();
}
...
}
private void releaseGoogleApiClient() {
if (googleApiClient != null && googleApiClient.isConnected()) {
Wearable.DataApi.removeListener(googleApiClient, onDataChangedListener);
googleApiClient.disconnect();
}
}
...
@Override
public void onConnected(Bundle bundle) {
Log.d(TAG, "connected GoogleAPI");
Wearable.DataApi.addListener(googleApiClient, onDataChangedListener);
Wearable.DataApi.getDataItems(googleApiClient).setResultCallback(onConnectedResultCallback);
}
private final DataApi.DataListener onDataChangedListener = new DataApi.DataListener() {
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
DataItem item = event.getDataItem();
processConfigurationFor(item);
}
}
dataEvents.release();
invalidateIfNecessary();
}
};
private void processConfigurationFor(DataItem item) {
if ("/simple_watch_face_config".equals(item.getUri().getPath())) {
DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
if (dataMap.containsKey("KEY_BACKGROUND_COLOUR")) {
String backgroundColour = dataMap.getString("KEY_BACKGROUND_COLOUR");
watchFace.updateBackgroundColourTo(Color.parseColor(backgroundColour));
}
if (dataMap.containsKey("KEY_DATE_TIME_COLOUR")) {
String timeColour = dataMap.getString("KEY_DATE_TIME_COLOUR");
watchFace.updateDateAndTimeColourTo(Color.parseColor(timeColour));
}
}
}
private final ResultCallback<DataItemBuffer> onConnectedResultCallback = new ResultCallback<DataItemBuffer>() {
@Override
public void onResult(DataItemBuffer dataItems) {
for (DataItem item : dataItems) {
processConfigurationFor(item);
}
dataItems.release();
invalidateIfNecessary();
}
};
@Override
public void onConnectionSuspended(int i) {
Log.e(TAG, "suspended GoogleAPI");
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.e(TAG, "connectionFailed GoogleAPI");
}
@Override
public void onDestroy() {
...
releaseGoogleApiClient();
super.onDestroy();
}
}
}
我們可以看到,每次watch face可見時,連接到數據層,一旦連接后,增加兩個監(jiān)聽器。數據層每次發(fā)生變化時,onDataChangedListener
將得到通知;當服務連接時,onConnectedResultCallback
將得到通知。在這兩種情況下,我們想處理接收到的DataItem - processConfigurationFor(DataItem)
。在處理Items時,為了抓到發(fā)送的值,使用path (/simple_watch_face_config
)和 關聯到每一項的keys (在SimpleWatchFaceConfigurationActivity
中定義了這些keys )。一旦項目被識別,傳遞它們到SimpleWatchFace
以更新顏色:
updateBackgroundColourTo(int colour)
:負責更新背景顏色
updateDateAndTimeColourTo(int colour)
:負責更新數據和時間的顏色 public class SimpleWatchFace {
...
private static final int DATE_AND_TIME_DEFAULT_COLOUR = Color.WHITE;
private static final int BACKGROUND_DEFAULT_COLOUR = Color.BLACK;
private int backgroundColour = BACKGROUND_DEFAULT_COLOUR;
private int dateAndTimeColour = DATE_AND_TIME_DEFAULT_COLOUR;
public static SimpleWatchFace newInstance(Context context) {
Paint timePaint = new Paint();
timePaint.setColor(DATE_AND_TIME_DEFAULT_COLOUR);
timePaint.setTextSize(context.getResources().getDimension(R.dimen.time_size));
timePaint.setAntiAlias(true);
Paint datePaint = new Paint();
datePaint.setColor(DATE_AND_TIME_DEFAULT_COLOUR);
datePaint.setTextSize(context.getResources().getDimension(R.dimen.date_size));
datePaint.setAntiAlias(true);
Paint backgroundPaint = new Paint();
backgroundPaint.setColor(BACKGROUND_DEFAULT_COLOUR);
return new SimpleWatchFace(timePaint, datePaint, backgroundPaint, new Time());
}
SimpleWatchFace(Paint timePaint, Paint datePaint, Paint backgroundPaint, Time time) {
this.timePaint = timePaint;
this.datePaint = datePaint;
this.backgroundPaint = backgroundPaint;
this.time = time;
}
public void draw(Canvas canvas, Rect bounds) {
time.setToNow();
canvas.drawRect(0, 0, bounds.width(), bounds.height(), backgroundPaint);
...
}
public void updateDateAndTimeColourTo(int colour) {
dateAndTimeColour = colour;
timePaint.setColor(colour);
datePaint.setColor(colour);
}
public void updateBackgroundColourTo(int colour) {
backgroundColour = colour;
backgroundPaint.setColor(colour);
}
...
}
最后剩下的是處理環(huán)境模式。在環(huán)境模式中,會默認設置為黑色和白色的顏色。
@Override
public void onAmbientModeChanged(boolean inAmbientMode) {
super.onAmbientModeChanged(inAmbientMode);
watchFace.setAntiAlias(!inAmbientMode);
watchFace.setShowSeconds(!isInAmbientMode());
if (inAmbientMode) {
watchFace.updateBackgroundColourToDefault();
watchFace.updateDateAndTimeColourToDefault();
} else {
watchFace.restoreBackgroundColour();
watchFace.restoreDateAndTimeColour();
}
invalidate();
startTimerIfNecessary();
}
每次進入環(huán)境模式我們更新默認的顏色,當它存在環(huán)境模式,我們必須恢復其選擇的顏色:
public class SimpleWatchFace {
...
public void updateBackgroundColourToDefault() {
backgroundPaint.setColor(BACKGROUND_DEFAULT_COLOUR);
}
public void updateDateAndTimeColourToDefault() {
timePaint.setColor(DATE_AND_TIME_DEFAULT_COLOUR);
datePaint.setColor(DATE_AND_TIME_DEFAULT_COLOUR);
}
public void restoreDateAndTimeColour() {
timePaint.setColor(dateAndTimeColour);
datePaint.setColor(dateAndTimeColour);
}
public void restoreBackgroundColour() {
backgroundPaint.setColor(backgroundColour);
}
}
現在你可以開始測試端到端的全部配置。首先在你的腕表上運行可穿戴模塊,然后是手機上的移動模塊。跳到你手機上的 Android Wear 應用程序,你可以選擇安裝腕表和訪問它的設置屏幕。從這里你可以改變背景以及日期和時間的顏色。