Chinese (Traditional) (中文(繁體)) translation by Zhang Xiang Liang (you can also view the original English article)
Android特別的功能之壹就是能夠自定義各個方面的用戶體驗。 當Android Wear首次在2014的Google I / O大會上推出時,許多開發人員和用戶發現,對於智能手表而言上面那個觀點並不正確,因為沒有創建watch face的官方API。 鑒於自定義手表的功能是用戶的關鍵需求之壹,因此開發人員發現在Android Wear中使用沒有官方文檔支持的黑客技術創建自己的watch face並不奇怪。
幸運的是,Google迅速讓每個人都知道官方API正在進行之中,並且在2014年12月API終於被發布給開發社區了。 在本文中,您將了解Android Wear的官方Watch Faces API,並實現壹個簡單的數字手表,您可以根據自己的需要進行擴展。 實現可能有點冗長,但您可以在GitHub上找到本文的示例應用程序。
1.設置IDE
需要做的第壹件事是Android Studio中設置watch face。 創建項目時,請選擇手機和平板電腦 ,使用最低SDK API 18,因為Android 4.3是支持捆綁Android Wear應用程序最低版本的操作系統。 您還需要選擇“Wear” 框,並選擇“最低API 21”。 您可以看到“目標Android設備” 屏幕應顯示如下 。
當您進入兩個“添加activity” 界面時,請為兩個界面選擇“不添加activity ”。


壹旦您單擊 完成,項目環境被構建了並擁有壹個mobile模塊和wear模塊。
2.建立wear服務
Android Wear通過使用WatchFaceService
來實現watch face。 在這篇文章中,將創建壹個CanvasWatchFaceService
類的繼承,它實現WatchFaceService
類,該類提供了繪制watch face的Canvas
。 首先 在Android Studio中的Wear模塊下創建壹個新的Java類 CanvasWatchFaceService
。

public class WatchFaceService extends CanvasWatchFaceService
創建完該類後,妳需要在本文的源文件中創建壹個內部類WatchFaceEngine
,該類繼承Engine
。 這是處理系統事件的watch face引擎,例如屏幕關閉或進入環境模式。
private class WatchFaceEngine extends Engine
當您的存根代碼為 WatchFaceEngine
,返回到外部類並重寫onCreateEngine
方法以返回您的新內部類。 這將使您的watch face服務與驅動顯示器的代碼相關聯。
@Override public Engine onCreateEngine() { return new WatchFaceEngine(); }
壹旦將裸機服務放在壹起,您就可以繼續更新AndroidManifest 文件的壹般內務管理任務, 以便Android Wear可以發現您的服務。 請記住,您當前的代碼將不會執行任何操作。 在進行壹些項目管理之後,我們將回到這個類並引導引擎。
3.更新AndroidManifest文件
在wear模塊中打開 AndroidManifest.xml文件 。 在頂端附近,您應該已經看到壹行代碼:
<uses-feature android:name="android.hardware.type.watch" />
在該行下方,我們需要添加壹個watch face的兩個必需權限。 這些要求是:
<uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
壹旦妳的權限被設置,妳將需要在 application
節點添加壹個有BIND_WALLPAPER
權限的服務節點,幾組 meta-data
包含watch face參考圖像的選擇屏幕(在這個例子中,我們只是使用啟動器圖標),還有intent-filter
讓系統知道您的服務是用於顯示watch face。
<service android:name=".service.CustomWatchFaceService" android:label="Tuts+ Wear Watch Face" 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="@mipmap/ic_launcher" /> <meta-data android:name="com.google.android.wearable.watchface.preview_circular" android:resource="@mipmap/ic_launcher" /> <intent-filter> <action android:name="android.service.wallpaper.WallpaperService" /> <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" /> </intent-filter> </service>
Wear清單完成後,妳需要打開mobile模塊的AndroidManifest.xml文件,並在wear模塊中添加我們的使用的兩種權限PROVIDE_BACKGROUND和WAKE_LOCK,因為Android Wear需要這兩者權限,無論是wear還是mobile模塊將wear
APK安裝在用戶手表上都需要該權限。 配置好這兩個清單文件,妳可以返回到 CustomWatchFaceService.java 來開始實現引擎。
4.啟動引擎
與服務相關聯的Engine
對象是用來驅動watch face的。 它處理定時器、顯示用戶界面、進出環境模式以及獲取關於物理手表顯示的信息。 簡而言之,這裏是魔術發生的地方。
步驟1:定義必要的值和變量
您將要做的第壹件事是在引擎中實現壹組成員變量,以跟蹤顯示器的設備狀態、定時器間隔和屬性。
//Member variables private Typeface WATCH_TEXT_TYPEFACE = Typeface.create( Typeface.SERIF, Typeface.NORMAL ); private static final int MSG_UPDATE_TIME_ID = 42; private long mUpdateRateMs = 1000; private Time mDisplayTime; private Paint mBackgroundColorPaint; private Paint mTextColorPaint; private boolean mHasTimeZoneReceiverBeenRegistered = false; private boolean mIsInMuteMode; private boolean mIsLowBitAmbient; private float mXOffset; private float mYOffset; private int mBackgroundColor = Color.parseColor( "black" ); private int mTextColor = Color.parseColor( "red" );
正如妳所看到的,我們定義了 TypeFace
,它用於數字手表文字以及watch face的背景顏色和文字顏色。 Time
對象用於(您猜到它了)跟蹤當前設備的時間。 mUpdateRateMs
用於控制我們需要實現的定時器,以便每秒更新壹次watch face(因此mUpdateRateMs
的值為1000毫秒),因為WatchFaceService
標準, 所以只能以1分鐘的增量跟蹤時間。 壹旦發動機知道手表的物理形狀mXOffset
和mYOffset
就被定義,使得我們的watch face可以被繪制而不是太靠近屏幕的頂部或左側或被圓角切斷。 三個布爾值用於跟蹤不同的設備和應用程序狀態。
您需要定義的下壹個對象是處理用戶可能正在旅行和更改時區情況的廣播接收器。 該接收器簡單地清除保存的時區並重置顯示時間。
final BroadcastReceiver mTimeZoneBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { mDisplayTime.clear( intent.getStringExtra( "time-zone" ) ); mDisplayTime.setToNow(); } };
在定義了接收器之後,您需要在引擎頂部創建的最終對象是Handler
,用來每隔壹秒更新壹次watch face。 這是必要的,由於上面討論的WatchFaceService
限制。 如果妳自己的watch face只需要每分鐘更新壹次,那麽妳可以放心地忽略這個部分。
private final Handler mTimeHandler = new Handler() { @Override public void handleMessage(Message msg) { switch( msg.what ) { case MSG_UPDATE_TIME_ID: { invalidate(); if( isVisible() && !isInAmbientMode() ) { long currentTimeMillis = System.currentTimeMillis(); long delay = mUpdateRateMs - ( currentTimeMillis % mUpdateRateMs ); mTimeHandler.sendEmptyMessageDelayed( MSG_UPDATE_TIME_ID, delay ); } break; } } } };
實現Handler
是非常簡單的。 它首先檢查消息ID。 如果匹配MSG_UPDATE_TIME_ID
,它將繼續使當前的重繪視圖無效。 視圖無效後,Handler
檢查屏幕是否可見並且不是環境模式。 如果它是可見的,它再次發送重復請求。 我們只是在手表可見並且不是環境模式的時候在Handler
中重復執行這個操作,因為每秒鐘保持更新會更耗電。 如果用戶沒有看到屏幕,我們只是回到每分鐘更新的WatchFaceService
。
步驟2:初始化引擎
現在您的變量和對象被聲明了,現在是開始初始化watch face的時候了。 Engine
有onCreate
方法用於創建可花費大量時間和電池的對象和其他任務。 您還將要為WatchFaceStyle
設置幾個標誌來控制當watch face處於活動狀態時系統與用戶的交互。
@Override public void onCreate(SurfaceHolder holder) { super.onCreate(holder); setWatchFaceStyle( new WatchFaceStyle.Builder( CustomWatchFaceService.this ) .setBackgroundVisibility( WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE ) .setCardPeekMode( WatchFaceStyle.PEEK_MODE_VARIABLE ) .setShowSystemUiTime( false ) .build() ); mDisplayTime = new Time(); initBackground(); initDisplayText(); }
對於示例應用程序,您將用setWatchFaceStyle
來設置通知卡的背景,以簡要顯示卡類型是否設置為中斷。 您還將設置窺視模式以便通知卡只占用必要的空間。
最後,要告訴系統不顯示默認時間,因為您將自己顯示它。 雖然這些只是可用的幾個選項,您可以在WatchFaceStyle.Builder
對象的官方文檔中找到更多信息。
WatchFaceStyle
設置完成後,可以將mDisplayTime
初始化為新Time
對象。
initBackground
和initDisplayText
分配兩個Paint
對象,它們是在在引擎頂部定義的。 然後設置背景和文本的顏色設置、文本的字體和字體大小設置。
private void initBackground() { mBackgroundColorPaint = new Paint(); mBackgroundColorPaint.setColor( mBackgroundColor ); } private void initDisplayText() { mTextColorPaint = new Paint(); mTextColorPaint.setColor( mTextColor ); mTextColorPaint.setTypeface( WATCH_TEXT_TYPEFACE ); mTextColorPaint.setAntiAlias( true ); mTextColorPaint.setTextSize( getResources().getDimension( R.dimen.text_size ) ); }
步驟3:處理設備狀態
接下來,您需要從Engine
類中實現由設備狀態更改觸發的各種方法。 我們先從 onVisibilityChanged
方法開始,當用戶隱藏或顯示watch face時,該方法被調用。
@Override public void onVisibilityChanged( boolean visible ) { super.onVisibilityChanged(visible); if( visible ) { if( !mHasTimeZoneReceiverBeenRegistered ) { IntentFilter filter = new IntentFilter( Intent.ACTION_TIMEZONE_CHANGED ); CustomWatchFaceService.this.registerReceiver( mTimeZoneBroadcastReceiver, filter ); mHasTimeZoneReceiverBeenRegistered = true; } mDisplayTime.clear( TimeZone.getDefault().getID() ); mDisplayTime.setToNow(); } else { if( mHasTimeZoneReceiverBeenRegistered ) { CustomWatchFaceService.this.unregisterReceiver( mTimeZoneBroadcastReceiver ); mHasTimeZoneReceiverBeenRegistered = false; } } updateTimer(); }
當調用此方法時,它會檢查看watch是否可見。 如果watch face可見,則會查看在Engine
頂部定義的BroadcastReceiver
是否已註冊。 如果不是,該方法將創建壹個IntentFilter
為ACTION_TIMEZONE_CHANGED
的行動並註冊BroadcastReceiver
監聽它。
如果watch face不可見,此方法將檢查是否可以取消註冊BroadcastReceiver
。 壹旦BroadcastReceiver
被處理,updateTimer
被調用來觸發使得watch face無效和重繪watch face。 updateTimer
停止任何Handler
待處理的操作並檢查是否應發送另壹個。
private void updateTimer() { mTimeHandler.removeMessages( MSG_UPDATE_TIME_ID ); if( isVisible() && !isInAmbientMode() ) { mTimeHandler.sendEmptyMessage( MSG_UPDATE_TIME_ID ); } }
步驟4:與可穿戴硬件合作
當您的服務與Android Wear相關聯時,onApplyWindowInsets
被調用。 這用於確定您的watch face正在運行的設備是圓形還是方形。 這可以讓您更改您的watch face以配合硬件。
在示例應用中調用此方法時,該方法將簡單地檢查設備形狀並更改用於繪制watch face的x偏移量,以確保您的watch face在設備上可見。
@Override public void onApplyWindowInsets(WindowInsets insets) { super.onApplyWindowInsets(insets); mYOffset = getResources().getDimension( R.dimen.y_offset ); if( insets.isRound() ) { mXOffset = getResources().getDimension( R.dimen.x_offset_round ); } else { mXOffset = getResources().getDimension( R.dimen.x_offset_square ); } }
您需要重寫的下壹個方法是 onPropertiesChanged
。 例如,如果設備支持老化保護或低位環境模式,則在確定Wear設備的硬件屬性時調用此方法。
在這該方法中,檢查這些屬性是否適用於運行watch face的設備,並將其保存在Engine
頂部定義的成員變量中。
@Override public void onPropertiesChanged( Bundle properties ) { super.onPropertiesChanged( properties ); if( properties.getBoolean( PROPERTY_BURN_IN_PROTECTION, false ) ) { mIsLowBitAmbient = properties.getBoolean( PROPERTY_LOW_BIT_AMBIENT, false ); } }
步驟5:在環境和靜音模式下節省電池
處理初始設備狀態後,需要實現 onAmbientModeChanged
和 onInterruptionFilterChanged
。 顧名思義, 當設備移入或移出環境模式時調用onAmbientModeChanged
。
如果設備處於環境模式,您將需要將手表的顏色更改為黑色和白色以便註意用戶的電池。 當設備從環境模式返回時,您可以重置手表的顏色。 您還需要註意要求低位環境支持的設備的anti-aliasing。 在所有標誌變量設置完畢後,您可以使表面無效並重繪,然後檢查是否應啟動壹秒計時器。
@Override public void onAmbientModeChanged(boolean inAmbientMode) { super.onAmbientModeChanged(inAmbientMode); if( inAmbientMode ) { mTextColorPaint.setColor( Color.parseColor( "white" ) ); } else { mTextColorPaint.setColor( Color.parseColor( "red" ) ); } if( mIsLowBitAmbient ) { mTextColorPaint.setAntiAlias( !inAmbientMode ); } invalidate(); updateTimer(); }
當用戶手動更改穿戴設備的中斷設置時,onInterruptionFilterChanged
將被調用。 發生這種情況時,您需要檢查設備是否靜音,然後相應地更改用戶界面。 在這種情況下,您將更改您的watch face的透明度,將Handler
設置為僅在設備靜音時每分鐘更新,然後重繪watch face。
@Override public void onInterruptionFilterChanged(int interruptionFilter) { super.onInterruptionFilterChanged(interruptionFilter); boolean isDeviceMuted = ( interruptionFilter == android.support.wearable.watchface.WatchFaceService.INTERRUPTION_FILTER_NONE ); if( isDeviceMuted ) { mUpdateRateMs = TimeUnit.MINUTES.toMillis( 1 ); } else { mUpdateRateMs = DEFAULT_UPDATE_RATE_MS; } if( mIsInMuteMode != isDeviceMuted ) { mIsInMuteMode = isDeviceMuted; int alpha = ( isDeviceMuted ) ? 100 : 255; mTextColorPaint.setAlpha( alpha ); invalidate(); updateTimer(); } }
當您的設備處於環境模式時,Handler
定時器將被禁用。 Watch face仍然可以每壹分鐘更新當前時間,通過使用內置的onTimeTick
方法來使Canvas
無效。
@Override public void onTimeTick() { super.onTimeTick(); invalidate(); }
步驟6:繪制watch face
考慮完上面所有的情況後,是時候最終畫出watch face了。 CanvasWatchFaceService
采用了標準的Canvas
對象,所以妳需要添加onDraw
到Engine
並且手動繪制出watch face。
在本教程中,我們只需要繪制壹段時間的文字表示,盡管您可以更改onDraw
以輕松支持模擬的watch face。 在該方法中,需要通過更新Time
對象來驗證是否顯示正確的時間,然後可以開始應用watch face了。
@Override public void onDraw(Canvas canvas, Rect bounds) { super.onDraw(canvas, bounds); mDisplayTime.setToNow(); drawBackground( canvas, bounds ); drawTimeText( canvas ); }
drawBackground
設置Wear設備背景使用純色。
private void drawBackground( Canvas canvas, Rect bounds ) { canvas.drawRect( 0, 0, bounds.width(), bounds.height(), mBackgroundColorPaint ); }
然而,drawTimeText
將在幾個輔助方法的幫助下創建要顯示的時間文本,然後將其應用於onApplyWindowInsets
定義的x和y偏移點的畫布。
private void drawTimeText( Canvas canvas ) { String timeText = getHourString() + ":" + String.format( "%02d", mDisplayTime.minute ); if( isInAmbientMode() || mIsInMuteMode ) { timeText += ( mDisplayTime.hour < 12 ) ? "AM" : "PM"; } else { timeText += String.format( ":%02d", mDisplayTime.second); } canvas.drawText( timeText, mXOffset, mYOffset, mTextColorPaint ); } private String getHourString() { if( mDisplayTime.hour % 12 == 0 ) return "12"; else if( mDisplayTime.hour <= 12 ) return String.valueOf( mDisplayTime.hour ); else return String.valueOf( mDisplayTime.hour - 12 ); }

結論
壹旦妳實現了繪制watch face的方法,妳應該具備出門所需的基本知識並制作自己的watch face。 Android Wear watch face的好處是,這只可能刮傷表面。
您可以在手表或手機上添加配置activity,將基於watch face的Canvas
替換為OpenGL實現,或者根據需要從WatchFaceService
派生自己的類 。
可以從用戶手機訪問其他API或信息,可能性似乎無止境。 獲得關於watch face的創意然後去享受吧。
Envato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post