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

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
