有些時(shí)候二驰,我們需要為自己的應(yīng)用添加一個(gè)桌面小部件來(lái)顯示一些簡(jiǎn)單但是關(guān)鍵的信息方便用戶使用探赫。應(yīng)用小部件的視圖是靠RemoteViews來(lái)展現(xiàn)的,而RemoteViews內(nèi)只能放置一些簡(jiǎn)單的ViewGroup和Widget。文章內(nèi)容基本上是看官方Guide纳击,如果英文較好的話牢酵,還是閱讀原文比較靠譜秸弛。
聲明你的應(yīng)用里有個(gè)小部件
首先医咨,你需要在你的Manifest文件中聲明一個(gè)receiver,可能會(huì)有人問(wèn):為什么是一個(gè)receiver而不是一個(gè)其他什么的比如appwidget這樣的標(biāo)簽(如果有的話)灰追。別著急堵幽,等下就明白了。
來(lái)看下官網(wǎng)的例子吧
<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>
AppWidgetProvider初探
其中android:name
屬性指定了一個(gè)類叫做ExampleAppWidgetProvider弹澎,那么這個(gè)類到底是干什么的朴下,來(lái)看下ExampleAppWidgetProvider類的代碼吧
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這個(gè)類苦蒿,這個(gè)類又是干什么呢殴胧,讓我們來(lái)看看它的繼承關(guān)系
這下就很清楚了,實(shí)際上它就是個(gè)BroadcastReceiver佩迟,所以之前在Manifest文件里我們用的tag是receiver团滥。它復(fù)寫了onReceive方法來(lái)處理收到廣播時(shí)分別調(diào)用哪個(gè)回調(diào)方法。AppWidgetProvider的回調(diào)方法有以下
-
onUpdate()
這個(gè)方法會(huì)周期性的被調(diào)用报强,時(shí)間值由AppWidgetInfo中的updatePeriodMillis值決定灸姊,可能你現(xiàn)在對(duì)這個(gè)屬性很陌生,別慌秉溉,我們下面會(huì)講到這個(gè)東西力惯,現(xiàn)在知道有這么個(gè)東西即可。onUpdate還會(huì)在每次小部件被添加到桌面時(shí)被調(diào)用坚嗜,所以我們需要在這個(gè)回調(diào)方法里做一些初始化的工作夯膀,比如設(shè)置remoteviews里一些控件的事件處理方法诗充,如果你指定了configuration Activity苍蔬,那么onUpdate在小控件被初次添加時(shí)不會(huì)被回調(diào),因?yàn)橹付薱onfiguration Activity之后蝴蜓,初始化的工作應(yīng)該在configuration中完成碟绑,但是onUpdate方法還是會(huì)響應(yīng)之后的一些更新操作俺猿。 -
onAppWidgetOptionsChanged()
這個(gè)方法看名字就很簡(jiǎn)單明了,當(dāng)小部件的選項(xiàng)改變時(shí)被調(diào)用格仲。
說(shuō)的更具體點(diǎn)押袍,就是部件被第一次添加到屏幕上及控件的大小被改變時(shí)該方法會(huì)被調(diào)用。我們可以通過(guò)getAppWidgetOptions()
方法來(lái)獲取一個(gè)Bundle對(duì)象凯肋,bundle中存了四個(gè)配置值
OPTION_APPWIDGET_MIN_WIDTH
OPTION_APPWIDGET_MIN_HEIGHT
OPTION_APPWIDGET_MAX_WIDTH
-
OPTION_APPWIDGET_MAX_HEIGHT
作用看名字就知道了谊惭,我就不贅述了。
-
onDelete(Context, int[])
小部件被從桌面上移除時(shí)被調(diào)用 -
onEnable(Context)
小部件第一次創(chuàng)建時(shí)被調(diào)用侮东,如果同時(shí)向桌面添加了兩個(gè)相同的小部件圈盔,這個(gè)方法只會(huì)在第一個(gè)小部件創(chuàng)建時(shí)被調(diào)用。 -
onDisabled(Context)
最后一個(gè)小部件從App Widget Host里移除時(shí)被調(diào)用悄雅,在這個(gè)方法里可以做一些清理工作 -
onReceive(Context, Intent)
這個(gè)方法上面也提到過(guò)了驱敲,跟一般BroadcastReceiver一樣,如果我們想要發(fā)送自定義的廣播宽闲,我們就需要復(fù)寫這個(gè)方法众眨,在這個(gè)方法里接收。
有這個(gè)多方法容诬,但是通常情況下娩梨,需要復(fù)寫的方法只有onUpdate
,回到上面的代碼览徒,可以看到這里有一個(gè)for循環(huán)姚建,官網(wǎng)的解釋是因?yàn)橐粋€(gè)小部件可以添加多個(gè)實(shí)例,而這些實(shí)例的appWidgetId都存在appWidgetIds這個(gè)數(shù)組里吱殉,因此通過(guò)這個(gè)循環(huán)掸冤,我們可以將所有的控件同步。之后的代碼跟通知一樣友雳,是為了設(shè)置remoteviews上的控件的事件處理稿湿,如果你的小部件不需要響應(yīng)點(diǎn)擊事件,只需要簡(jiǎn)單的new一個(gè)RemoteViews然后調(diào)用appWidgetManager的updateAppWidget方法就可以了押赊。
AppWidgetProviderInfo初探
AppWidgetProviderInfo定義了一些對(duì)小部件來(lái)說(shuō)很重要的屬性饺藤,我們需要在res/xml
目錄下新建一個(gè)xml文件
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp"
android:minHeight="40dp"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/preview"
android:initialLayout="@layout/example_appwidget"
android:configure="com.example.android.ExampleAppWidgetConfigure"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen">
</appwidget-provider>
下面來(lái)對(duì)這個(gè)xml里的屬性進(jìn)行講解
- minWidth:小部件的最小寬度
- minHeight:小部件的最小高度
- updatePeriodMilllis:控件更新時(shí)間
- previewImage:在小部件列表里顯示的略縮圖
- initialLayout:自定義布局的xml文件
- configure:前文提到的configration Activity文件資源
-
resizeMode:定義了小部件可以被橫向或縱向拉伸或壓縮,可選值為
vertial
或horizontal
-
widgetCategory:定義了小部件可以被添加的地方流礁,可選值為
home_screen
和keyguard
涕俗,前者是桌面,后者是鎖屏界面神帅,鎖屏界面的小部件需要在設(shè)置里開(kāi)啟啟用鎖屏小部件的選項(xiàng)(測(cè)試手機(jī)為L(zhǎng)39h 系統(tǒng)4.4)
這里需要注意一些地方:Android的桌面被分為4x4的方格再姑,如果minWidth和minHeight不等于整數(shù)個(gè)方格的邊長(zhǎng),系統(tǒng)會(huì)對(duì)小部件的長(zhǎng)和寬進(jìn)行向上取整找御;忘了哪篇博客上看的元镀,好像說(shuō)系統(tǒng)為了省電绍填,最小的更新時(shí)間為30分鐘,也就是updatePeriod屬性設(shè)置成比30分鐘小的值并不會(huì)提早觸發(fā)onUpdate栖疑,guide中也說(shuō)了并不保證時(shí)間的準(zhǔn)確性讨永,guide里建議不要太頻繁更新,更新的頻率最好不要超過(guò)1小時(shí)/次遇革,這樣可以節(jié)省電量卿闹。而且因?yàn)椴煌男】丶?duì)于更新的需求不一致,最好是讓用戶自己去調(diào)整更新時(shí)間萝快。
如果在update的時(shí)間到來(lái)時(shí)比原,手機(jī)正處在休眠的狀態(tài),那么手機(jī)將會(huì)被喚醒來(lái)進(jìn)行一次更新杠巡。如果更新的頻率不要超過(guò)1小時(shí)/次量窘,對(duì)電池不會(huì)造成太大損耗,如果需要經(jīng)常地更新小部件或者不需要在手機(jī)休眠時(shí)更新小部件的話氢拥,可以采用AlarmManager來(lái)定時(shí)蚌铜,AlarmManager中有兩種方法:
ELAPSED_REALTIME
和RTC
是不會(huì)將手機(jī)喚醒的,這樣只有手機(jī)在正常使用時(shí)才會(huì)收到alarm的通知嫩海。此時(shí)updatePeriodMillis應(yīng)該設(shè)置為0
一點(diǎn)小遺憾
看完本文冬殃,你就可以寫一個(gè)簡(jiǎn)單的小部件添加到桌面啦。不過(guò)還有很多沒(méi)解決的問(wèn)題叁怪,比如想添加Edittext但發(fā)現(xiàn)remoteviews不支持這個(gè)控件审葬,想嘗試通過(guò)代碼實(shí)現(xiàn)添加小部件到桌面,但是不知道如何獲取launcher的appWidgetHost奕谭,只好暫時(shí)放棄涣觉。如果有同學(xué)對(duì)這方面有了解,可以留言交流一下血柳。