詳解從零開始創(chuàng)建Android主屏幕Widget
創(chuàng)建Android主屏幕Widget是本文要介紹的內(nèi)容,主要是來了解Android Widget 的創(chuàng)建過程,文中很詳細的講解了Android Widget創(chuàng)建過程,一起來看本文詳解。
當最基本的控件,如Clock和PictureFrameHomeScreen,隨第一款Android手機的發(fā)布后,Android用戶就開始嘗試編寫各種應用Widget(小工具)了,隨著WidgetAPI的公開,為開發(fā)人員帶來了全新有趣的開發(fā)模式,除了傳統(tǒng)的電話應用外,還可以做其它方面的應用開發(fā)。
開發(fā)人員可以使用WidgetAPI(包含在Android1.5中,最新版本已經(jīng)到Android2.0了)創(chuàng)建簡單的控件,然后在新的Widget中顯示和使用這些控件。
本文向你介紹如何從零開始創(chuàng)建一個主屏幕應用Widget,通過使用AlarmManager接口,以用戶設(shè)定的時間間隔更新圖片。你將看到如何創(chuàng)建一個Widget,以及如何隨機地從一組圖片中選擇一張圖片顯示,根據(jù)用戶設(shè)定的時間間隔周期性改變顯示的圖片。
創(chuàng)建一個簡單的Widget包括以下幾個步驟:
1、創(chuàng)建一個RemoteView,由它為Widget提供用戶界面;
2、將RemoteView綁定一個Activity(行為)實現(xiàn)AppWidgetProvider接口;
3、在Androidmanifest配置文件中提供Widget的關(guān)鍵配置信息。
項目準備
一個Widget就是一個處理特定行為的BroadcastReceiver,AppWidgetProvider接口為開發(fā)人員提供了一個框架來簡化處理這些行為,它包括以下方法:
1、onEnabled():創(chuàng)建第一個Widget時調(diào)用,如果可以,應在這里進行全局初始化。
2、onDisabled():它和onEnabled()相反,創(chuàng)建最后一個Widget時才調(diào)用它,如果可以,應在這里進行全局清理。
3、onUpdate():當Widget需要更新它的View時調(diào)用,用戶第一次創(chuàng)建Widget時也需要調(diào)用它。
4、onDeleted():當Widget的一個特定實例被刪除時調(diào)用,清理特定實例應放在這里進行。
5、onReceive():此方法默認情況下處理BroadcastReceiver行為,并調(diào)用上面的方法(警告:根據(jù)相關(guān)文檔記載,需要開發(fā)人員自己處理某些特殊情況,更多信息請看下面的說明)。
作者注:點擊此鏈接(http://groups.google.com/group/android-developers/browse_thread/thread/365d1ed3aac30916/e405ca19df2170e2?pli=1)檢查有關(guān)AppWidget框架的缺陷信息,討論內(nèi)容包括解決此問題的代碼(也可從本文下載示例代碼,http://www.developer.com/img/2009/08/3833306_appwidget_code.zip),如果它們并不存在,可以將Widget標識符傳遞給onUpdate()方法。
為了讓Android識別Widget,需要在manifest文件中加入一個標準的標記,下面的代碼片段顯示了一個示例:
- receiver android:name=“ImagesWidgetProvider”
- intent-filter
- action
- android:name=“android.appwidget.action.APPWIDGET_UPDATE” /
- /intent-filter
- meta-data
- android:name=“android.appwidget.provider”
- android:resource=“@xml/imageswidget_info” /
- /receiver
你可能已經(jīng)注意到,和常見的定義不一樣,小節(jié)引用了一個XML文件資源,這個文件為Widget定義了額外的數(shù)據(jù),與AppWidgetProviderInfo類一致,這里定義的信息是不變的,因此這個例子不包括updatePeriodMillis的值,因為這個程序允許用戶修改與更新時間,如果你在這里分配updatePeriodMillis,它就不能這樣做。下面是imageswidget_info.xml文件的完整代碼:
- ?xml version=“1.0” encoding=“utf-8”?
- appwidget-provider
- xmlns:android=“http://schemas.android.com/apk/res/android”
- android:minWidth=“146dp”
- android:minHeight=“146dp”
- android:initialLayout=“@layout/widget”
- android:configure=
- “com.mamlambo.imageswidget.ImagesWidgetConfiguration” /
標記定義了Widget的大小,默認布局和創(chuàng)建Widget實例時的啟動行為配置,為了讓Widget在主屏幕上更好地顯示,Widget必須保持一定的大小,主屏幕分為特定大小的單元格,Google提供的基本原則是用你想占用的單元格數(shù)量乘以74,再減去2。在這個例子中,Widget應該是一個正方形,長和寬都各占兩個單元格,因此大小就是74*2-2=146.
實現(xiàn)onUpdate()
不顯示內(nèi)容的Widget是沒有用的,幸好這個Widget的RemoteView對象很容易實現(xiàn),它使用一組存儲在應用程序的drawable資源目錄下的圖片,程序使用R.drawable.[imagename]引用這些圖片資源,代碼需要創(chuàng)建一個數(shù)組容納圖片的名字,這樣從這些圖片名字中隨機選擇一個就可以了。下面的代碼片段顯示了onUpdate()的實現(xiàn),它隨機顯示一張圖片:
- @Override
- public void onUpdate(Context context,
- AppWidgetManager appWidgetManager,
- int[] appWidgetIds) {
- for (int appWidgetId : appWidgetIds) {
- int imageNum = (new
- java.util.Random().nextInt(IMAGES.length));
- RemoteViews remoteView = new
- RemoteViews(context.getPackageName(),
- R.layout.widget);
- remoteView.setImageViewResource(
- R.id.image, IMAGES[imageNum]);
- appWidgetManager.updateAppWidget(
- appWidgetId, remoteView);
- }
- }
注意onUpdate()方法使用Widget實例列表作為最后的參數(shù),每個實例必須分開處理,由于AppWidget框架的現(xiàn)有缺陷,有些實例可能不可見或不能啟用,但在這個例子中可以忽略這些問題。請記住,你自己在實現(xiàn)時可能想跟蹤哪個Widget是真正激活的。
你可以下載本文提供的代碼(http://www.developer.com/img/2009/08/3833306_appwidget_code.zip),查看其中的R.layout.widgetXML布局定義,它基本上只是一個ImageView,RemoteViews只能使用一組有限的View對象,包括Button,ImageButton,ImageView,TextView,AnalogClock,Chronometer和ProgressBar,并且只能在FrameLayout,LinearLayout或RelativeLayout內(nèi)使用。請保持RemoteView簡單,因為訪問是通過setImageViewResource()和setTextViewText()方法控制的,它們的目的是在另一個進程內(nèi)畫一個View,因此你的應用程序比正常布局要少些控制。
自此,Widget最基本的部分完成了,但為了讓用戶配置圖片更新之間的時間間隔,你必須要實現(xiàn)配置
Activity,然后處理RemoteView更新的調(diào)度問題。
實現(xiàn)Widget的Activity配置
和manifest文件中定義的ImagesWidgetConfiguration類似,任何Activity配置都有兩個特殊情況:
1、只要一啟動,它的目的就是返回結(jié)果,因此必須要調(diào)用setResult()方法返回一個適當?shù)慕Y(jié)果(結(jié)果要么是RESULT_CANCELED或RESULT_OK)。
2、在設(shè)置結(jié)果時,Widget標識值必須放在額外引用的AppWidgeManager.EXTRA_APPWIDGET_ID中。
下面的代碼片段顯示了如何處理這兩個例外,如果用戶中途退出Activity,調(diào)用setResult()將默認值設(shè)為RESULT_CANCELED。
- Bundle extras = launchIntent.getExtras();
- if (extras != null) {
- appWidgetId = extras.getInt(
- AppWidgetManager.EXTRA_APPWIDGET_ID,
- AppWidgetManager.INVALID_APPWIDGET_ID);
- Intent cancelResultValue = new Intent();
- cancelResultValue.putExtra(
- AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
- setResult(RESULT_CANCELED, cancelResultValue);
- }
除了這兩個限制外,你可能還要實現(xiàn)你喜歡的Activity配置,圖1顯示了這個例子的簡單Activity配置,如果你RESULT_CANCELED,Widget不會顯示給用戶,如果你返回RESULT_OK,用戶才看得見Widget,你可以使用任意存儲機制保存Widget實例的配置數(shù)據(jù),這個例子使用的是SharedPreferences接口,存儲Widget標識更新之間的時間,你可以下載本文配套的代碼查看完整的實現(xiàn)(http://www.developer.com/img/2009/08/3833306_appwidget_code.zip)。

圖1配置界面:這個簡單的配置界面讓用戶設(shè)置圖片刷新間隔時間
實現(xiàn)更新調(diào)度
正如前面提到的,AppWidgetProviderInfo類中的配置值是不變的,因為updateTimeMillis值就在這個類中,任何有AppWidgetProviderInfo的Widget實例,只要設(shè)置了這個值,Widget就會根據(jù)其頻率更新,沒有辦法修改它,因為這個應用程序應該讓用戶配置Widget中圖片刷新的頻率,因此你必須自動動手實現(xiàn)。
AlarmManager類是最常用的更新機制,因為它支持重復通知,這些通知是將被觸發(fā)的簡單的PendingIntent對象。
你可能會想你可以創(chuàng)建一個具有AppWidgetManager.ACTION_APPWIDGET_UPDATE的Intent對象,然后給特定的Widget標識符設(shè)置額外的值,接著你就可以通過調(diào)用AlarmManager的setRepeating()方法,采用調(diào)度機制反復地更新,遺憾的是,這種做法行不通,Android系統(tǒng)反復使用Intents匹配行為和方案值,那些“額外的”值是不會拿去對比的。實際上,解決方案非常簡單:首先為你的Widget定義一個方案,然后用它定義唯一的Intent實例。下面的代碼片段顯示如何實現(xiàn)這個目標:
- Intent widgetUpdate = new Intent();
- widgetUpdate.setAction(
- AppWidgetManager.ACTION_APPWIDGET_UPDATE);
- widgetUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS,
- new int[] { appWidgetId });
- // make this pending intent unique
- widgetUpdate.setData(
- Uri.withAppendedPath(Uri.parse(
- ImagesWidgetProvider.URI_SCHEME + “://widget/id/”),
- String.valueOf(appWidgetId)));
- PendingIntent newPending = PendingIntent.getBroadcast(
- getApplicationContext(), 0, widgetUpdate,
- PendingIntent.FLAG_UPDATE_CURRENT);
- // now schedule it
- AlarmManager alarms = (AlarmManager) getApplicationContext()。
- getSystemService(Context.ALARM_SERVICE);
- alarms.setRepeating(AlarmManager.ELAPSED_REALTIME,
- SystemClock.elapsedRealtime(), updateRateSeconds * 1000,
- newPending);
在ImagesWidgetConfigurationActivity中你會發(fā)現(xiàn)前面的代碼,manifest文件顯示塊處理這個特定的scheme,你也需要在AppWidgetProvider接口的onDeleted()方法內(nèi)停止重復的警報。最后,當手機重啟后,更新調(diào)度也必須重啟。
在AppWidgetProvider的onReceive()方法內(nèi)提供了一個簡單的解決方案,首先,檢查更新行為,如果它是一個更新,確保它不包含scheme值,如果它不是更新,那么調(diào)度AlarmManager的PendingIntent不會觸發(fā)這個行為,在這種情況下,檢查每個Widget標識符是否有一個配置選項值,如果沒有,那么在配置Widget之前,你知道這個更新行為被接收到。但如果選項值有效,那么你知道可以調(diào)度PendingIntent,在onReceive()方法中你可以看到完整的邏輯實現(xiàn)。
這個方案允許多個Widget同時顯示,如圖2所示。更新也可以以不同的頻率進行,每個顯示的圖片是從圖片集中隨機選擇的。

圖2多個Widget實例:這里顯示了兩個不同的同時運行的實例
至此,你已經(jīng)看到了如何在Android平臺上創(chuàng)建一個基本的Widget,每個Widget實例按獨立的方式調(diào)度,由用戶自行配置,但AppWidge框架不直接支持,因此需要自動動手編碼實現(xiàn)。
小結(jié):詳解從零開始創(chuàng)建Android主屏幕Widget的內(nèi)容介紹完了,希望通過Android Widget 創(chuàng)建內(nèi)容的學習能對你有所幫助!