簡介
App Widget:應(yīng)用程序窗口小部件,微型的應(yīng)用程序視圖,它可以被嵌入到其它應(yīng)用程序中礼仗,比如桌面祸憋,并接收周期性的更新会宪。你可以通過一個 App Widget Provider 來發(fā)布一個 Widget,比如時鐘蚯窥、天氣掸鹅、音樂播放器等等±乖可以容納 Widget 的應(yīng)用叫做 App Widget Host巍沙,詳細(xì)參考App Widgets| Android Developers。
App Widget Provider是Android中提供的用于實現(xiàn)桌面小工具的類荷鼠,其本質(zhì)是一個廣播句携,即BroadcastReceiver。
創(chuàng)建一個 App Widget 的主要步驟
- 在 AndroidManifest 中聲明 App Widget
- 在 xml 目錄定義 App Widget 的初始化 xml 文件
- 實現(xiàn) Widget 具體布局的 Layout xml允乐。
- 繼承 AppWidgetProvider 類矮嫉,實現(xiàn)具體的 Widget 業(yè)務(wù)邏輯削咆。
在 AndroidManifest 中聲明 App Widget
<!-- 聲明widget對應(yīng)的AppWidgetProvider -->
<!--android:name屬性聲明的就是 Widget 所用的 AppWidgetProvider 類-->
<receiver android:name=".common.ExampleAppWidgetProvider">
<intent-filter>
<!--所有的窗口小部件都接收android.appwidget.action.APPWIDGET_UPDATE 動作的廣播,
該廣播根據(jù)android:updatePeriodMillis設(shè)定的間隔時間發(fā)出廣播蠢笋,用于定時更新桌面上的所有窗口小部件拨齐。-->
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<!--定義一個自定義的動作廣播,可以通過在該廣播接收器中注冊自定義的動作以使窗口小部件接收自定義的廣播昨寞。-->
<action android:name="com.skywang.widget.UPDATE_ALL"/>
</intent-filter>
<!--聲明了 Widget 的 AppWidgetProviderInfo 對應(yīng)的資源 xml 的位置瞻惋,用的是 xml 目錄下的 example_appwidget_info.xml。-->
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/example_appwidget_info"/>
</receiver>
在 xml 目錄定義 App Widget 的初始化 xml 文件
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="300dp"
android:minHeight="72dp"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/ic_launcher"
android:initialLayout="@layout/example_appwidget"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen|keyguard">
<!--
android:minWidth : 最小寬度
android:minHeight : 最小高度
android:updatePeriodMillis : 更新widget的時間間隔(ms)援岩,"86400000"為1個小時
android:previewImage : 預(yù)覽圖片
android:initialLayout : 加載到桌面時對應(yīng)的布局文件
android:resizeMode : widget可以被拉伸的方向歼狼。horizontal表示可以水平拉伸,vertical表示可以豎直拉伸
android:widgetCategory : widget可以被顯示的位置窄俏。home_screen表示可以將widget添加到桌面蹂匹,keyguard表示widget可以被添加到鎖屏界面。
android:initialKeyguardLayout : 加載到鎖屏界面時對應(yīng)的布局文件
-->
</appwidget-provider>
minWidth & minHeight:定義了 Widget 的最小寬高凹蜈,當(dāng) minWidth 和 minHeight 不是桌面 cell 的整數(shù)倍時限寞,Widget 的寬高會被闊至與其最接近的 cells 大小。Google 官方給出了一個大致估算 minWidth & minHeight 的公式仰坦,根據(jù) Widget 所占的 cell 數(shù)量來計算寬高:70 × n ? 30
履植,n 是所占的 cell 數(shù)量。
updatePeriodMillis:定義了 Widget 的刷新頻率悄晃,也就是 App Widget Framework 多久請求一次 AppWidgetProvider 的 onUpdate() 回調(diào)函數(shù)玫霎。該時間間隔并不保證精確,出于節(jié)約用戶電量的考慮妈橄,Android 系統(tǒng)默認(rèn)最小更新周期是 30 分鐘庶近,也就是說:如果您的程序需要實時更新數(shù)據(jù),設(shè)置這個更新周期是 2 秒眷蚓,那么您的程序是不會每隔 2 秒就收到更新通知的鼻种,而是要等到 30 分鐘以上才可以,要想實時的更新 Widget沙热,一般可以采用 Service 和 AlarmManager 對 Widget 進(jìn)行更新叉钥。
previewImage:當(dāng)用戶選擇添加 Widget 時的預(yù)覽圖片。如果該屬性沒有定義篙贸,則展示 application 的 launcher icon投队。該屬性是在 3.0 以后引入的。
initialLayout:Widget 的布局 Layout 文件爵川。
configure:定義了用戶在添加 Widget 時彈出的配置頁面的 Activity敷鸦,用戶可以在此進(jìn)行 Widget 的一些配置,該 Activity 是可選的,如果不需要可以不進(jìn)行聲明轧膘。
resizeMode:Widget 在水平和垂直方向是否可以調(diào)整大小钞螟,值可以為:horizontal(水平方向可以調(diào)整大小)谎碍,vertical(垂直方向可以調(diào)整大辛郾酢),none(不可以調(diào)整大畜〉怼)拯啦,也可以 horizontal|vertical 組合表示水平和垂直方向均可以調(diào)整大小。
widgetCategory:表示 Widget 可以顯示的位置熔任,包括 home_screen(桌面)褒链,keyguard(鎖屏),keyguard 屬性需要 5.0 或以上 Android 版本才可以疑苔。
其它更多詳細(xì)屬性可以參考 AppWidgetProviderInfo甫匹。
繼承 AppWidgetProvider 類
AppWidgetProvider 繼承自 BroadcastReceiver,內(nèi)部邏輯非常簡單惦费,就是在 onReceive() 中處理 Widget 相關(guān)的廣播事件(ACTION_APPWIDGET_UPDATE, ACTION_APPWIDGET_DELETED, ACTION_APPWIDGET_ENABLED, ACTION_APPWIDGET_DISABLED, ACTION_APPWIDGET_OPTIONS_CHANGED)分發(fā)到各個回調(diào)函數(shù)中(onUpdate(), onDeleted(), onEnabled(), onDisabled, onAppWidgetOptionsChanged())兵迅。
onUpdate():是最重要的回調(diào)函數(shù),根據(jù) updatePeriodMillis 定義的定期刷新操作會調(diào)用該函數(shù)薪贫,此外當(dāng)用戶添加 Widget 時 也會調(diào)用該函數(shù)恍箭,可以在這里進(jìn)行必要的初始化操作。但如果在<appwidget-provider>
中聲明了 android:configure 的 Activity瞧省,在用戶添加 Widget 時扯夭,不會調(diào)用 onUpdate(),需要由 configure Activity 去負(fù)責(zé)去調(diào)用 AppWidgetManager.updateAppWidget() 完成 Widget 更新鞍匾,后續(xù)的定時更新還是會繼續(xù)調(diào)用 onUpdate() 的交洗。
onDeleted():當(dāng) Widget 被刪除時調(diào)用該方法。
onEnabled():當(dāng) Widget 第一次被添加時調(diào)用橡淑,例如用戶添加了兩個你的 Widget藕筋,那么只有在添加第一個 Widget 時該方法會被調(diào)用。所以該方法比較適合執(zhí)行你所有 Widgets 只需進(jìn)行一次的操作梳码。
onDisabled():與 onEnabled 恰好相反,當(dāng)你的最后一個 Widget 被刪除時調(diào)用該方法伍掀,所以這里用來清理之前在 onEnabled() 中進(jìn)行的操作掰茶。
onAppWidgetOptionsChanged():當(dāng) Widget 第一次被添加或者大小發(fā)生變化時調(diào)用該方法,可以在此控制 Widget 元素的顯示和隱藏蜜笤。
示例代碼:
public class ExampleAppWidgetProvider extends AppWidgetProvider {
private static final String TAG = "ExampleAppWidget";
// 啟動ExampleAppWidgetService服務(wù)對應(yīng)的action
private final Intent EXAMPLE_SERVICE_INTENT =
new Intent("android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE");
// 更新 widget 的廣播對應(yīng)的action
private final String ACTION_UPDATE_ALL = "com.skywang.widget.UPDATE_ALL";
// 保存 widget 的id的HashSet濒蒋,每新建一個 widget 都會為該 widget 分配一個 id。
private static Set idsSet = new HashSet();
// onUpdate() 在更新 widget 時,被執(zhí)行沪伙,
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
Log.d(TAG, "onUpdate(): appWidgetIds.length="+appWidgetIds.length);
// 每次 widget 被創(chuàng)建時瓮顽,對應(yīng)的將widget的id添加到set中
for (int appWidgetId : appWidgetIds) {
idsSet.add(Integer.valueOf(appWidgetId));
}
PreferencesUtils.putString(context, Keys.IDSSET, CharFunction.toJSONString(idsSet));
updateAllAppWidgets(context, AppWidgetManager.getInstance(context), idsSet);
}
// 當(dāng) widget 被初次添加 或者 當(dāng) widget 的大小被改變時,被調(diào)用
@Override
public void onAppWidgetOptionsChanged(Context context,
AppWidgetManager appWidgetManager, int appWidgetId,
Bundle newOptions) {
updateAllAppWidgets(context, AppWidgetManager.getInstance(context), idsSet);
}
// widget被刪除時調(diào)用
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
Log.d(TAG, "onDeleted(): appWidgetIds.length="+appWidgetIds.length);
// 當(dāng) widget 被刪除時围橡,對應(yīng)的刪除set中保存的widget的id
for (int appWidgetId : appWidgetIds) {
idsSet.remove(Integer.valueOf(appWidgetId));
}
PreferencesUtils.putString(context,Keys.IDSSET, CharFunction.toJSONString(idsSet));
super.onDeleted(context, appWidgetIds);
}
// 第一個widget被創(chuàng)建時調(diào)用
@Override
public void onEnabled(Context context) {
Log.d(TAG, "onEnabled");
updateAllAppWidgets(context, AppWidgetManager.getInstance(context), idsSet);
super.onEnabled(context);
}
// 最后一個widget被刪除時調(diào)用
@Override
public void onDisabled(Context context) {
Log.d(TAG, "onDisabled");
// 在最后一個 widget 被刪除時暖混,終止服務(wù)
context.stopService(EXAMPLE_SERVICE_INTENT);
super.onDisabled(context);
}
// 接收廣播的回調(diào)函數(shù)
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
Log.d(TAG, "OnReceive:Action: " + action);
if (ACTION_UPDATE_ALL.equals(action)) {
// “更新”廣播
updateAllAppWidgets(context, AppWidgetManager.getInstance(context), idsSet);
}
super.onReceive(context, intent);
}
// 更新所有的 widget
private void updateAllAppWidgets(Context context, AppWidgetManager appWidgetManager, Set set) {
Log.d(TAG, "updateAllAppWidgets(): size="+set.size());
// widget 的id
int appID;
// 迭代器,用于遍歷所有保存的widget的id
Iterator it = set.iterator();
while (it.hasNext()) {
appID = ((Integer)it.next()).intValue();
// 獲取 example_appwidget.xml 對應(yīng)的RemoteViews
RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.example_appwidget);
remoteView.setTextViewText(R.id.tv_gongli, CalendarUtil.getAllInfoHtml(Calendar.getInstance()));
remoteView.setOnClickPendingIntent(R.id.tv_gongli, getPendingIntent(context, R.id.tv_gongli));
// 更新 widget
appWidgetManager.updateAppWidget(appID, remoteView);
}
}
private PendingIntent getPendingIntent(Context context, int buttonId) {
Intent intent = new Intent();
intent.setClass(context, ExampleAppWidgetProvider.class);
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
intent.setData(Uri.parse("custom:" + buttonId));
PendingIntent pi = PendingIntent.getBroadcast(context, 0, EXAMPLE_SERVICE_INTENT, 0 );
return pi;
}
}
創(chuàng)建 App Widget Configuration Activity
如果你的 Widget 需要用戶配置一些選項翁授,你可以為你的 Widget 創(chuàng)建 Configuration Activity拣播,當(dāng)用戶添加 Widget 時會自動彈出該 Activity。Configuration Activity 和普通 Activity 一樣需要在 Manifest 中聲明收擦,但是需要額外聲明一個 intent-filter: APPWIDGET_CONFIGURE贮配,例如:
<activity android:name=".ExampleAppWidgetConfigure">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>
同時還需要在上述的 appwidget-provider 中聲明:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
...
android:configure="com.example.android.ExampleAppWidgetConfigure"
... >
</appwidget-provider>
有兩點需要注意的是:
- Activity 必須返回帶 EXTRA_APPWIDGET_ID 的 result。
- 聲明Configuration Activity 后 onUpdate() 在 Widget 添加時不會被調(diào)用塞赂,Activity 負(fù)責(zé)調(diào)用 AppWidgetManager.updateAppWidget() 完成 Widget 更新泪勒。
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
}
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
RemoteViews views = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
appWidgetManager.updateAppWidget(mAppWidgetId, views);
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
參考:
http://www.reibang.com/p/985547afd22f
http://glgjing.github.io/blog/2015/11/05/android-kai-fa-zhi-app-widget-xiang-jie/
https://blog.csdn.net/yangwen123/article/details/8042499