文章內容
1. 什麼是App Widgets
2. App Widgets的基本要素
3. 創建App Widgets的基本步驟
3.1 聲明應用小部件(App Widgets)
3.2 設置應用小部件提供器信息(AppWidgetProviderInfo)
3.3 定義應用小部件提供器(擴展AppWidgetProvider或擴展BroadcastReceiver)
3.4 創建應用小部件佈局(App Widgets Layout)
1. 什麼是App Widgets
App Widgets就是微型的應用程序視圖,它可以被內嵌在其他的應用程序視圖內,並可以接收週期性的更新以改
變其(App Widgets)外觀表象。App Widgets則是由應用小部件提供器(AppWidgetProvider)進行發佈。顧名
思義,那些能夠持有應用小部件的應用組件(application component)則可稱之爲小部件宿主。
下圖是一個常見的小部件,部件的宿主則是Home Screen。
2.App Widgets的基本要素
要想創建一個小部件,需要具備以下要素:
1. 應用小部件提供器信息對象(AppWidgetProviderInfoobject)
它描述了一個小部件的元數據,諸如小部件的引用佈局,更新頻率,大小,以及部件提供器類(
AppWidgetProvider)類,此類應該以XML方式定義,並放置在res/xml/目錄下。
2. 小部件提供器(AppWidgetProvider)類
通常,應該定義一個擴展了AppWidgetProvider的新類,在新類中定義一些基本的方法用來與小部件進行程序
上的基於廣播事件的交互。當小部件更新,有效,禁用及被刪除時,這個類便可以接收到廣播。
3. 小部件視圖佈局(View layout)
此外,還可以實現一個部件配置活動(an App Widget configuration Activity),此配置活動在
用戶將部件放置宿主視圖上的瞬間被加載,同過它可以對小部件進行配置,諸如大小,內容顯示等。
3. 創建App Widgets的基本步驟
下面介紹創建一個小部件的基本步驟.
3.1 聲明應用小部件(App Widgets)
首先,在應用程序的AndroidManifest.xml文件裏聲明應用小組件類(此類爲擴展了AppWidgetProvider的新
類),例如:
<receiver android:name=".WordWidget" android:label="@string/widget_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.test.appwidget.action_updated" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/widget_word" />
</receiver>
<receiver>元素需要指定android:name屬性,它指定由小部件使用的AppWidgetProvider的名字。
<intent-filter>元素必須包含一個帶有android:name屬性的<action>元素。該屬性指定AppWidgetProvider
接受ACTION_APPWIDGET_UPDATE廣播。這是唯一的須要在此顯式地聲明的系統廣播。應用組件管理器根據需要
給小部件發送其他的廣播事件。
<meta-data>元素指定的AppWidgetProviderInfo的資源和描述符, 並需要如下屬性:
android:name - 元數據名。 這裏使用了android.appwidgets.provider來表示數據作爲AppWidgetProviderInof的描述符。
android:resource - AppWidgetProviderInof資源的位置。
3.2 設置應用小部件提供器信息(AppWidgetProviderInfo)
AppWidgetProviderInfo
定義了小部件的基本屬性,譬如它的最小布局尺寸,初始的佈局資源,更新頻率,及
在部件被創建時啓動的配置活動(可選)。AppWidgetProviderInfo
對象的是在XML文件內使用一個單獨的
<appwidget-provider>元素進行定義的,並將該XML文件保存在工程的res/xml/目錄下。
下面是一個AppWidgetProviderInfo
對象的定義:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="294dp"
android:minHeight="72dp"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/preview"
android:initialLayout="@layout/widget_message"
android:configure="com.example.android.ExampleAppWidgetConfigure"
android:resizeMode="horizontal|vertical">
</appwidget-provider>
<appwidget-provider>元素下的可用屬性可以對應到appWidgetProviderInof類下的相應成員,接下來只簡單介紹上述屬性:
1. android:minWidth和android:minHeight
這兩個屬性值指定了在默認情況下部件所佔用的最小空間。Home screen能將應用小部件定位在其窗口上是基
於一組網格式的單元格,這些單元格擁有明確的高度和寬度。如果小部件的最小寬度和最小高度與單元格尺寸不匹
配,此時部件的大小會被調整到與單元格大小最爲接近的尺寸。
提醒:爲了使小部件可以在多個設備上擁有良好的用戶體驗,部件的最小尺寸應該絕不要比4x4個單元格大小。
2. android:
updatePeriodMillis
該屬性指定部件多久(單位:毫秒)需要更新
一次
,更新時調用AppWidgetProvider
內的onUpdate()
回調方
法。同時,部件管理器也在時間上對多久更新部件一次做了限制,如果該屬性值小於30分鐘,部件將不會被更新。因
此,實際的更新動作並不能保證在
屬性值所指定的時間間隔上發生。爲了節省設備電池的電量消updatePeriodMillis
耗,儘可能低不要頻繁地更新部件。建議部件一個小時不超過一次更新。當然,也可以根據實際需要在部件配置中設
置更新週期,或者通過設置一個Alarm來更新部件,通過這些方式設置的更新週期將打破部件管理器對更新週期的限
制。
提醒:如果在需要更新部件時設備正處於休眠狀態,此時設備將被喚醒。如果想頻繁地更新部件,或者在設備處於休眠狀態時
不更新部件。爲此,我們可以基於一個警報(Alarm)來執行部件更新,這使得部件在需要更新時,不會把處於休眠狀態的設備喚
醒。爲了做到這一點,可以在應用部件提供器(擴展AppWidgetProvider的新類)內重寫基類的onEnabled方法內用設置一個重複
式警報(Alarm),類型設置爲ELAPSED_REALTIME或者RTC,然後將setupdatePeriodMillis設置爲0。在onEnabled內設置警報的
目的是爲保證該警報只被設置一次。
3. android:initialLayout
該屬性通過資源引用的方式指明瞭佈局資源,在該佈局資源內定義了部件的初始佈局。
4. android:configure
配置屬性定義了在添加應用部件時加載的活動, 通過該配置活動可以使用戶對部件屬性進行配置。配置活動的存在與否是可選的。
5. android:previewImage
該屬性指定了一個部件的預覽視圖,它是在配置之後當選擇這個部件時看起來像的樣子。當沒有爲部件提供預覽
視圖時,看的到樣子是應用的啓動圖標。
6. android:resizeMode
該屬性是由Android 3.1引入的,它指定了調整部件大小的處理規則。在宿主窗口長按住部件時,就會看到調整
部件大小的操作把手,然後水平或者豎直地拖動把手來改變部件的大小。resizeMode的有效值包括"horizontal",
"vertical", and "none",或是它們聯合方式,譬
如
。 "horizontal|vertical"
關於<appwidget-provider>元素更多的可用屬性可以參考appWidgetProviderInfo類。除了上述提到的屬
性,其它未提到的屬性會在接下來的文章內逐一進行說明,演示。
3.3 定義應用小部件提供器(擴展AppWidgetProvider或擴展BroadcastReceiver)
3.3.1 擴展AppWidgetProvider
擴展AppWidgetProvider的目的是爲了把它作爲一個便利類使用,進而減少了用戶代碼內的工作。同時,可以
根據需要對基類( AppWidgetProvider)內的事件方法進行重寫。 AppWidgetProvider類擴展了
BroadcastReceiver類,因而可以處理廣播事件。不過,它只接受那些和部件有關聯的廣播,例如當部件被更新,刪
除。當此類廣播事件發生時,AppWidgetProvider便會接收它們,並調用以下方法:
onDeleted(Context , int[])
每次部件從宿主內被刪除時將會調用此方法,如果有n個此部件從宿主內被刪除,該方法則被調用n次。
onEnabled(Context)
當部件的實例第一次被創建時會調用此方法。例如,如果添加了兩個此部件到宿主內,則該方法只在第一個此部
件被添加時調用。因此,如果需要爲此部件的所有實例執行打開某個數據庫或者執行某些設置的操作,且此類操作對
所有實例僅發生一次,則該方法是執行這些操作的最好地方。
onDisabled(Context)
和onEnabled(Context)方法相反,該方法只有在部件的最後一個實例從宿主內移除時調用。因此,這裏也是執
行清理工作的地方,例如關閉數據庫,斷開網絡連接等。
onReceive(Context, Intent)
該方法在廣播事件發生時,且在上述方法被調用之前被調用。通常不必實現此方法,默認情況下,在基類
(appWidgetProvider)內對部件事件進行過濾,並相應地調用了上述方法。
提醒:在Android 1.5中有個衆所周知的問題,即在onDeleted()方法本該被調用時,它並不會被調用。爲了解決這個問題,可
以在appWidgetProvider擴展類內實現onReceive()方法,並在此方法內對ACTION_APPWIDGET_DELETED方法進行過濾,然後手
動調用onDeleted()方法。但別忘記依然需要調用基類(appWidgetProvider)的onReceive()方法.
onUpdate():
當部件被添加到宿主窗口內時將會調用該方法,接下來就是每逢部件需要更新時也會調用此方法。由於部件被添
加到宿主窗口內時將會調用該方法,因此在該方法內應該執行必要的設置,譬如爲視圖定義事件處理方式,開啓一個
臨時的服務。然而,如果我們定義了部件配置活動,當添加該部件時,此方法不會被調用。但是在後續的更新事件發
生時,仍然會調用該方法。因爲存在配置活動時,它來負載部件的第一次更新。
提醒:onUpdate()方法是
AppWidgetProvider內最重要的回調方法,因爲每當把部件加入到宿主內時都會調用該方法(除非
使用了部件配置活動)。如果部件需要接受用戶的交互事件,則需要在該方法內註冊事件處理函數。此外,如果部件不需要創建臨
時文件或數據庫,或者執行其他不需要清理的工作,那麼在AppWidgetProvider 擴展類內只需要定義該方法。
上述回調對應的廣播事件可參考:http://developer.android.com/reference/android/appwidget
下面是隻實現了onUpdate()方法
的擴展類實例:
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);
}
}
}
通過該擴展類可知:
1. 如果部件不需要臨時文件,或打開數據庫等操作,只實現了onUpdate()方法就可以了;
2. 在onUpdate()方法內對由該部件提供器創建的且已添加到宿主內的所有部件實例ID進行迭代;
3. 對每個迭代到的部件實例獲取其遠程視圖;
4. 對部件內的按鈕(R.id.button)註冊事件處理程序,當在宿主內點擊該按鈕時,ExampleActivity活動啓動;
5. 同理, 可以對部件內其他視圖或按鈕進行設置;
6. 使用指定的部件實例ID對部件進行更新;
7. 一次onUpdate()方法調用可以對所有該部件實例進行更新;
8. 由(7)可知,所有的此部件實例使用一個updatePeriodMillis進行更新;
9. 由(8)可知,最後一次此部件實例的創建
不會重置此前的updatePeriodMillis
(如果不使用部件配置活
動)。例如:更新週期updatePeriodMillis
爲每兩個小時一次,如果部件的第二個實例在部件的第一個實例創建一個
小時候才被創建。在部件的第一個實例被創建兩個小時後,這兩個部件實例都會被更新,因此第二實例的
updatePeriodMillis
被忽略了。
提醒:因爲AppWidgetProvider
擴展了BroadcastReceiver
, 所以受BroadcastReceiver生命週期的影響,
在此回調在返回之
前不能保證程序可以保持運行。例如在部件創建的過程中需要執行網絡數據請求,並且要求程序等待請求完成後繼續運行。鑑於
此類情況時,可以考慮在onUpdate方法內啓動一個服務來執行網絡數據的請求工作,然後onUpdate方法隨後返回。因而不用擔心
由於ANR錯誤的發生而導致組件提供器被關閉。
3.3.2 擴展BroadcastReceiver
由於appWidgetProvider只是擴展了BroadcastRecevier, 並在其內部對事件進行過濾後然後再調用相應的方
法。如果我們的擴展類需要接收部件廣播事件,只要直接擴展BroadcastRecevier便可,同時重寫onReceive方法,
並根據需要定義事件處理方法。
下面是自定義應用小部件提供器的簡單實現:
public class WidgetUpdatedReciever extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent)
{
if(intent.getAction().equals(AppWidgetManager.ACTION_APPWIDGET_UPDATE))
{
//handle the event here
}
else if(intent.getAction().equals(AppWidgetManager.ACTION_APPWIDGET_ENABLED))
{
//handle the event here
}
else if(intent.getAction().equals(AppWidgetManager.ACTION_APPWIDGET_DISABLED))
{
//handle the event here
}
else if(intent.getAction().equals(AppWidgetManager.ACTION_APPWIDGET_DELETED))
{
//handle the event here
}
}
}
3.4 創建應用小部件佈局(App Widgets Layout)
應用部件佈局是基於遠程視圖的(RemoteView
), 因此,部件佈局並不支持每種佈局或控件視圖。部件佈局可以
使用以下視圖對象:
佈局類:
FrameLayout
LinearLayout
RelativeLayout
控件類(不包括以下類的派生類):
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
下面是一個小部件佈局, 它使用了RelativeLayout佈局,幷包含有TextView和ImageView控件視圖:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
style="@style/WidgetBackground">
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:src="@drawable/star_logo" />
<TextView
android:id="@+id/word_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="14dip"
android:layout_marginBottom="1dip"
android:includeFontPadding="false"
android:singleLine="true"
android:ellipsize="end"
style="@style/Text.WordTitle" />
<TextView
android:id="@+id/word_type"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/word_title"
android:layout_toLeftOf="@id/icon"
android:layout_alignBaseline="@id/word_title"
android:paddingLeft="4dip"
android:includeFontPadding="false"
android:singleLine="true"
android:ellipsize="end"
style="@style/Text.WordType" />
<TextView
android:id="@+id/bullet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/word_title"
android:paddingRight="4dip"
android:includeFontPadding="false"
android:singleLine="true"
style="@style/BulletPoint" />
<TextView
android:id="@+id/definition"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/word_title"
android:layout_toRightOf="@id/bullet"
android:paddingRight="5dip"
android:paddingBottom="4dip"
android:includeFontPadding="false"
android:lineSpacingMultiplier="0.9"
android:maxLines="4"
android:fadingEdge="vertical"
style="@style/Text.Definition" />
</RelativeLayout>
最後,通過上述介紹後,我們應該可以創建一個自己應用小部件。在文章《應用小部件(App Widget)---- 基礎篇(2)》中,主要通過分析一個實例代碼來鞏固上述內容。
4. 參考資料
1. http://developer.android.com/guide/topics/appwidgets/index.html
2. http://developer.android.com/resources/samples/WiktionarySimple/index.html
2012年4月26日, 晚畢