Android開發(fā)中的窗口小部件App Widget
本文和大家重點(diǎn)討論一下Android開發(fā)指南-窗口小部件App Widgets ,應(yīng)用程序窗口小部件Widget開發(fā)是微小的應(yīng)用程序視圖,可以被嵌入到其它應(yīng)用程序中(比如桌面)并接收周期性的更新。
Android開發(fā)指南-窗口小部件App Widgets
應(yīng)用程序窗口小部件App Widgets
應(yīng)用程序窗口小部件Widget開發(fā)是微小的應(yīng)用程序視圖,可以被嵌入到其它應(yīng)用程序中(比如桌面)并接收周期性的更新。你可以通過一個App Widget provider來發(fā)布一個Widget??梢匀菁{其它App Widget的應(yīng)用程序組件被稱為App Widget宿主。下面的截屏顯示了一個音樂App Widget。
這篇文章描述了如何使用App Widget Provider發(fā)布一個App Widget。
基礎(chǔ)知識
The Basics為了創(chuàng)建一個App Widget,你需要下面這些:
AppWidgetProviderInfo 對象
描述一個App Widget元數(shù)據(jù),比如App Widget的布局,更新頻率,以及AppWidgetProvider 類。這應(yīng)該在XML里定義。
AppWidgetProvider 類的實(shí)現(xiàn)
定義基本方法以允許你編程來和App Widget連接,這基于廣播事件。通過它,當(dāng)這個App Widget被更新,啟用,禁用和刪除的時候,你都將接收到廣播通知。
視圖布局
為這個App Widget定義初始布局,在XML中。
另外,你可以實(shí)現(xiàn)一個App Widget配置活動。這是一個可選的活動Activity,當(dāng)用戶添加App Widget時加載并允許他在創(chuàng)建時來修改App Widget的設(shè)置。#p#
下面的章節(jié)描述了如何建立這些組件:
在清單中聲明一個應(yīng)用小部件
首先,在應(yīng)用程序AndroidManifest.xml文件中聲明AppWidgetProvider 類,比如:
復(fù)制到剪貼板 XML/HTML代碼
- <receiver android:name="ExampleAppWidgetProvider" >
- <intent-filter>
- <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
- </intent-filter>
- <meta-data android:name="android.appwidget.provider"
- android:resource="@xml/example_appwidget_info
- < srcsrc="http://hi.images.csdn.net/js/blog/tiny_mce/themes/advanced/langs/zh.js" type="text/javascript">
- < srcsrcsrcsrc="http://hi.images.csdn.net/js/blog/tiny_mce/plugins/syntaxhl/langs/zh.js" type="text/javascript"> " />
- </receiver>
<receiver>元素需要Android:name屬性,它指定了App Widget使用的AppWidgetProvider 。
<intent-filter> 元素必須包括一個含有android:name屬性的<action>元素。該元素指定AppWidgetProvider接受ACTION_APPWIDGET_UPDATE 廣播。這是唯一你必須顯式聲明的廣播。當(dāng)需要的時候,AppWidgetManager 會自動發(fā)送所有其他App Widget廣播給AppWidgetProvider。
<meta-data> 元素指定了AppWidgetProviderInfo 資源并需要以下屬性:
◆android:name – 指定元數(shù)據(jù)名稱。
◆android:resource – 指定AppWidgetProviderInfo 資源路徑。
增加AppWidgetProviderInfo元數(shù)據(jù)
AppWidgetProviderInfo定義一個App Widget的基本特性,比如最小布局尺寸,初始布局資源,刷新頻率,以及(可選的)創(chuàng)建時加載的一個配置活動。使用單獨(dú)的一個<appwidget-provider>元素在XML資源里定義AppWidgetProviderInfo 對象并保存到項(xiàng)目的res/xml/目錄下。
比如:
復(fù)制到剪貼板 XML/HTML代碼
- <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
- android:minWidth="294dp" <!-- density-independent pixels -->
- android:minHeight="72dp"
- android:updatePeriodMillis="86400000" <!-- once per day -->
- android:initialLayout="@layout/example_appwidget"
- android:configure="com.example.android.ExampleAppWidgetConfigure" >
- </appwidget-provider>
下面是<appwidget-provider>屬性的總結(jié):
◆minWidth 和minHeight 屬性的值指定了這個App Widget布局需要的最小區(qū)域。
缺省的App Widgets所在窗口的桌面位置基于有確切高度和寬度的單元網(wǎng)格。如果App Widget的最小長寬和這些網(wǎng)格單元的尺寸不匹配,那么這個App Widget將收縮到最接近的單元尺寸。(參見App Widget Design Guidelines 以獲取更多關(guān)于桌面單元尺寸的信息)
因?yàn)樽烂娌季址较颍ㄓ纱耍瑔卧某叽纾┛梢宰兓?,按照拇指?guī)則,你應(yīng)該假設(shè)最壞情況單元尺寸是74像素高和寬。不過,你必須從***的尺寸中減去2以把像素計(jì)算過程中產(chǎn)生的任何的整數(shù)舍入誤差考慮在內(nèi)。要找到像素密度無關(guān)的最小寬度和高度,使用這個公式:
(number of cells * 74) - 2
遵循這個公式,你應(yīng)該使用72dp為每一個單元高度,294dp為四個單元寬度。
◆updatePerdiodMillis 屬性定義了App Widget框架調(diào)用onUpdate()方法來從AppWidgetProvider請求一次更新的頻度。實(shí)際更新時間并不那么精確,而且我們建議更新頻率越低越好-也許每小時不超過一次以節(jié)省電源。你也許還會允許用戶在配置中調(diào)整這個頻率-一些人可能想每15分鐘一次股票報(bào)價,或者一天只要四次。
◆initialLayout屬性指向定義App Widget布局的資源。
◆configure屬性定義了Activity ,當(dāng)用戶添加App Widget時啟動,以為他或她配置App Widget特性。這是可選的(閱讀下面的Creating an App Widget Configuration Activity)。
參見AppWidgetProviderInfo 類以獲取更多可以被<appwidget-provider>元素接受的屬性信息。
創(chuàng)建App Widget開發(fā)布局
你必須在XML中為你的App Widget定義一個初始布局并保存到項(xiàng)目的res/layout/ 目錄下。你可以使用如下所列的視圖對象來設(shè)計(jì)你的App Widget,但是在此之前,請先閱讀并理解App Widget Design Guidelines.
如果你熟悉在XML中聲明布局,那么創(chuàng)建這個App Widget布局是很簡單的。但是,你必須意識到那個App Widget布局是基于RemoteViews, 這并不支持所有類型的布局或視圖小部件。
一個RemoteViews對象(以及,相應(yīng)的,一個App Widget)可以支持下面這個布局類:
以及下面的小部件類:
◆FrameLayout
◆LinearLayout
◆RelativeLayout
◆AnalogClock
◆Button
◆Chronometer
◆ImageButton
◆ImageView
◆ProgressBar
◆TextView
不支持這些類的派生。#p#
使用AppWidgetProvider類
你必須通過在清單文件中使用<receiver>元素來聲明你的AppWidgetProvider 類實(shí)現(xiàn)為一個廣播接收器(參見上面的Declaring an App Widget in the Manifest)。
AppWidgetProvider 類擴(kuò)展BroadcastReceiver 為一個簡便類來處理App Widget廣播。AppWidgetProvider只接收和這個App Widget相關(guān)的事件廣播,比如這個App Widget被更新,刪除,啟用,以及禁用。當(dāng)這些廣播事件發(fā)生時,AppWidgetProvider 將接收到下面的方法調(diào)用:
onUpdate(Context, AppWidgetManager, int[])
這個方法調(diào)用來間隔性的更新App Widget,間隔時間用AppWidgetProviderInfo 里的updatePeriodMillis屬性定義(參見添加AppWidgetProviderInfo元數(shù)據(jù))。這個方法也會在用戶添加App Widget時被調(diào)用,因此它應(yīng)該執(zhí)行基礎(chǔ)的設(shè)置,比如為視圖定義事件處理器并啟動一個臨時的服務(wù)Service,如果需要的話。但是,如果你已經(jīng)聲明了一個配置活動,這個方法在用戶添加App Widget時將不會被調(diào)用,而只在后續(xù)更新時被調(diào)用。配置活動應(yīng)該在配置完成時負(fù)責(zé)執(zhí)行***次更新。(參見下面的創(chuàng)建一個App Widget配置活動Creating an App Widget Configuration Activity。)
onDeleted(Context, int[])
當(dāng)App Widget從宿主中刪除時被調(diào)用。
onEnabled(Context)
當(dāng)一個App Widget實(shí)例***次創(chuàng)建時被調(diào)用。比如,如果用戶添加兩個你的App Widget開發(fā)實(shí)例,只在***次被調(diào)用。如果你需要打開一個新的數(shù)據(jù)庫或者執(zhí)行其他對于所有的App Widget實(shí)例只需要發(fā)生一次的設(shè)置,那么這里是完成這個工作的好地方。
onDisabled(Context)
當(dāng)你的App Widget的***一個實(shí)例被從宿主中刪除時被調(diào)用。你應(yīng)該在onEnabled(Context)中做一些清理工作,比如刪除一個臨時的數(shù)據(jù)庫。
onReceive(Context, Intent)
這個接收到每個廣播時都會被調(diào)用,而且在上面的回調(diào)函數(shù)之前。你通常不需要實(shí)現(xiàn)這個方法,因?yàn)槿笔〉腁ppWidgetProvider 實(shí)現(xiàn)過濾所有App Widget 廣播并恰當(dāng)?shù)恼{(diào)用上述方法。
注意: 在Android 1.5中, 有一個已知問題,onDeleted()方法在該調(diào)用時不被調(diào)用。為了規(guī)避這個問題,你可以像Group post中描述的那樣實(shí)現(xiàn)onReceive() 來接收這個onDeleted()回調(diào)。
最重要的AppWidgetProvider 回調(diào)函數(shù)是onUpdated(), 因?yàn)樗窃诿總€App Widget添加進(jìn)宿主時被調(diào)用的(除非你使用一個配置活動)。如果你的App Widget 要接受任何用戶交互事件,那么你需要在這個回調(diào)函數(shù)中注冊事件處理器。如果你的App Widget不創(chuàng)建臨時文件或數(shù)據(jù)庫,或者執(zhí)行其它需要清理的工作,那么onUpdated() 可能是你需要定義的唯一的回調(diào)函數(shù)。比如,如果你想要一個帶一個按鈕的App Widget,當(dāng)點(diǎn)擊時啟動一個活動,你可以使用下面的AppWidgetProvider實(shí)現(xiàn):
- public class ExampleAppWidgetProvider extends AppWidgetProvider {
- public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
- final int N = appWidgetIds.length;
- // Perform this loop procedure for each App Widget that belongs to this provider
- for (int i=0; i<N; i++) {
- int appWidgetId = appWidgetIds[i];
- // Create an Intent to launch ExampleActivity
- Intent intent = new Intent(context, ExampleActivity.class);
- PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
- // Get the layout for the App Widget and attach an on-click listener to the button
- RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
- views.setOnClickPendingIntent(R.id.button, pendingIntent);
- // Tell the AppWidgetManager to perform an update on the current App Widget
- appWidgetManager.updateAppWidget(appWidgetId, views);
- }
- }
- }
這個AppWidgetProvider 僅定義了onUpdated() 方法,為了定義一個PendingIntent,來啟動一個活動并使用setOnClickPendingIntent(int, PendingIntent)方法把它附著到這個App Widget的按鈕上。注意它包含了一個遍歷appWidgetIds中所有項(xiàng)的循環(huán),這是一個IDs數(shù)組,每個ID用來標(biāo)識由這個Provider創(chuàng)建的一個App Widget。這樣,如果用戶創(chuàng)建多于一個這個App Widget的實(shí)例,那么它們將被同步更新。不過,對于所有的App Widget實(shí)例,只有一個updatePeriodMillis 時間表被管理。比如,如果這個更新時間表被定義為每隔兩個小時,而且App Widget的第二個實(shí)例是在***個后面一小時添加的,那么它們將按照***個所定義的周期來更新而第二個被忽略(它們將都是每2個小時進(jìn)行更新,而不是每小時)。
注意: 因?yàn)檫@個AppWidgetProvider 是一個廣播接收器BroadcastReceiver,不能保證你的進(jìn)程在回調(diào)函數(shù)返回后仍然繼續(xù)運(yùn)行(參見應(yīng)用程序基礎(chǔ)>廣播接收器的生命周期Application Fundamentals > Broadcast Receiver Lifecycle以獲取更多信息)。如果你的App Widget設(shè)置過程能持續(xù)幾秒鐘(也許當(dāng)執(zhí)行網(wǎng)頁請求時)而且你要求你的進(jìn)程繼續(xù),考慮在onUpdated()方法里啟動一個服務(wù)Service 。從這個服務(wù)里,你可以執(zhí)行自己的App Widget更新,而不必?fù)?dān)心AppWidgetProvider 由于一個應(yīng)用程序無響應(yīng)錯誤Application Not Responding (ANR)而關(guān)閉。參見Wiktionary sample's AppWidgetProvider,這是個App Widget運(yùn)行一個Service的例子。
同樣參見ExampleAppWidgetProvider.Java 例子類。#p#
接收App Widget廣播意圖
AppWidgetProvider 只是一個簡便類。如果你想直接接收App Widget 廣播,你可以實(shí)現(xiàn)自己的BroadcastReceiver 或者重寫 onReceive(Context, Intent) 回調(diào)函數(shù)。你需要注意的4個意圖如下:
◆ACTION_APPWIDGET_UPDATE
◆ACTION_APPWIDGET_DELETED
◆ACTION_APPWIDGET_ENABLED
◆ACTION_APPWIDGET_DISABLED
創(chuàng)建一個App Widget 配置活動
如果你想讓用戶在添加一個新的App Widget時調(diào)整設(shè)置,你可以創(chuàng)建一個App Widget開發(fā)配置活動。這個活動將被App Widget宿主自動啟動并允許用戶在創(chuàng)建時配置可用的設(shè)置,比如App Widget顏色,尺寸,更新周期或者其它功能設(shè)置。
這個配置活動應(yīng)該在Android清單文件中聲明為一個通用活動。不過,它將被通過ACTION_APPWIDGET_CONFIGURE活動而被App Widget宿主啟動,因此這個活動需要接受這個意圖。比如:
- <activity android:name=".ExampleAppWidgetConfigure">
- <intent-filter>
- <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
- </intent-filter>
- </activity>
同樣的,活動必須在AppWidgetProviderInfo XML 文件中聲明,通過android:configure屬性(參見上面的添加AppWidgetProviderInfo元數(shù)據(jù)Adding the AppWidgetProviderInfo Metadata)。比如,配置活動可以聲明如下:
- <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
- ...
- android:configure="com.example.android.ExampleAppWidgetConfigure"
- ... >
- </appwidget-provider>
注意這個活動是用全名聲明的,因?yàn)樗鼘哪愕某绦虬獗灰谩?/p>
這就是所有關(guān)于配置活動你一開始需要了解的?,F(xiàn)在你需要一個真實(shí)的活動。這兒就有,不過,當(dāng)你實(shí)現(xiàn)這個活動時記住兩件重要的事情:
◆ App Widget 宿主調(diào)用配置活動而且配置活動應(yīng)該總是返回一個結(jié)果.這個結(jié)果應(yīng)該包含這個通過啟動該活動的意圖傳遞的App Widget ID(以EXTRA_APPWIDGET_ID保存在意圖的附加段Intent extras中)
◆ 當(dāng)這個 App Widget 被創(chuàng)建時將不會調(diào)用onUpdate() 方法(當(dāng)一個配置活動啟動時,系統(tǒng)將不會發(fā)送ACTION_APPWIDGET_UPDATE廣播).配置活動應(yīng)該在 App Widget ***次被創(chuàng)建時負(fù)責(zé)從AppWidgetManager請求一個更新.不過, onUpdate() 將在后續(xù)更新中被調(diào)用-只忽略***次.
參見下面章節(jié)的代碼片斷,該示例說明了如何從配置中返回一個結(jié)果并更新這個App Widget. #p#
從配置活動中更新一個App Widget
當(dāng)一個App Widget使用一個配置活動,那么當(dāng)配置結(jié)束時,就應(yīng)該由這個活動來更新這個App Widget.你可以直接AppWidgetManager里請求一個更新來這么做.
下面是恰當(dāng)?shù)母翧pp Widget 以及關(guān)閉配置活動這個過程的一個概要描述:
1,首先,從啟動這個活動的意圖中獲取App Widget ID:
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
}
2,實(shí)施你的App Widget開發(fā)配置。
3,當(dāng)配置完成后,通過調(diào)用getInstance(Context)獲取一個AppWidgetManager實(shí)例:
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
4,以一個RemoteViews布局調(diào)用updateAppWidget(int, RemoteViews)更新App Widget:
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.example_appwidget);
appWidgetManager.updateAppWidget(mAppWidgetId, views);
5,***,創(chuàng)建返回意圖,設(shè)置活動結(jié)果,并結(jié)束這個活動:
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
提示: 當(dāng)你的配置活動***次打開時,設(shè)置活動結(jié)果為RESULT_CANCELED。這樣,如果用戶在結(jié)束之前從活動外返回,這個App Widget 宿主會接收到配置取消通知而不會添加這個App Widget。參見ApiDemos里面的ExampleAppWidgetConfigure.java 例子。
【編輯推薦】
- AndroidWidget開發(fā)系列解讀
- AndroidWidget開發(fā)詳解
- 全面認(rèn)識WebWidget開發(fā)
- DashBoard的Widget開發(fā)指南
- 解析AndroidWidget開發(fā)中如何構(gòu)建Activity類