Android開發(fā)5:應用程序窗口小部件App Widgets的實現(xiàn)(附demo)
前言
本次主要是實現(xiàn)一個Android應用,實現(xiàn)靜態(tài)廣播、動態(tài)廣播兩種改變 widget內(nèi)容的方法,即在上篇博文中實驗的基礎上進行修改,所以此次實驗的重點是AppWidget小部件的實現(xiàn)啦~
首先,我們簡單說一下Widget是一個啥玩意~
應用程序窗口小部件(Widget)是微小的應用程序視圖,可以被嵌入到其它應用程序中(比如桌面)并接收周期性的更新。你可以通過一個App Widget provider來發(fā)布一個Widget??梢匀菁{其它App Widget的應用程序組件被稱為App Widget宿主。
Widget是在桌面上的一塊顯示信息的東西,也通過單擊Widget跳轉(zhuǎn)到一個程序里面。而系統(tǒng)自帶的程序,典型的Widget是music,這個Android內(nèi)置的音樂播放小程序。這個是典型的Widget+app應用。就是一個程序既可以通過Widget啟動,也可以通過App啟動。Widget就是一個AppWidgetProvider+一個UI界面顯示(預先綁定了好多Intent),界面上的信息可以通過程序控制而改變,單擊Widget,上的控件只能激發(fā)發(fā)送一個Intent,或發(fā)出一個Service的啟動通知。而AppWidgetProvider可以攔截這個Intent,而進行相應的處理(比如顯示新的信息)。
基礎知識
為了創(chuàng)建一個App Widget,你需要下面這些:
AppWidgetProviderInfo 對象
描述一個App Widget元數(shù)據(jù),比如App Widget的布局,更新頻率,以及AppWidgetProvider 類。這應該在XML里定義。
AppWidgetProvider 類的實現(xiàn)
定義基本方法以允許你編程來和App Widget連接,這基于廣播事件。通過它,當這個App Widget被更新,啟用,禁用和刪除的時候,你都將接收到廣播通知。
視圖布局
為這個App Widget定義初始布局,在XML中。
另外,你可以實現(xiàn)一個App Widget配置活動。這是一個可選的活動Activity,當用戶添加App Widget時加載并允許他在創(chuàng)建時來修改App Widget的設置。
widget 的添加:長按菜單鍵,點擊 widgets 選項。找到對應的 widget 將其拖入桌面。對 于不同的 API 版本顯示會稍有不同。
典型的 Android Widget 有三個主要組件,一個邊框、一個框架和圖形控件以及其他元素。 在 Android Studio 中創(chuàng)建 Widget 類后,會直接生成相關文件。

首先,在應用程序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> 元素必須包括一個含有android:name屬性的<action>元素。該元素指定AppWidgetProvider接受ACTION_APPWIDGET_UPDATE 廣播。這是唯一你必須顯式聲明的廣播。當需要的時候,AppWidgetManager 會自動發(fā)送所有其他App Widget廣播給AppWidgetProvider。
<meta-data> 元素指定了AppWidgetProviderInfo 資源并需要以下屬性:
- android:name – 指定元數(shù)據(jù)名稱。
- android:resource – 指定AppWidgetProviderInfo 資源路徑。
1. Widget 布局文件 widget_demo.xml,布局中有一個 ImageView,一個 TextView。 要求:文字顏色為紅色,大小為 20dp,整體背景為透明。最后效果如下:
2.增加AppWidgetProviderInfo元數(shù)據(jù)
AppWidgetProviderInfo定義一個App Widget的基本特性,比如最小布局尺寸,初始布局資源,刷新頻率,以及(可選的)創(chuàng)建時加載的一個配置活動。使用單獨的一個<appwidget-provider>元素在XML資源里定義AppWidgetProviderInfo 對象并保存到項目的res/xml/目錄下。
Widget 內(nèi)容提供者文件 widget_demo_info.xml,編輯該文件,設置其大小屬性和布 局,如下圖:
其中,minWidth 為最小寬度,minHeight 為最小高度,initialLayout 為初始布局。
3. 修改 WidgetDemo.java 代碼,重寫 onUpdate 方法,為 Widget 添加事件,使得能夠返 回主頁面。
這里需要使用到一種用戶程序訪問主屏幕和修改特定區(qū)域內(nèi)容的方法:RemoteView 架 構 。RemoteView 架構允許用戶程序更新主屏幕的 View,點擊 Widget 激活點擊事件,Android 會將其轉(zhuǎn)發(fā)給用戶程序,由 AppWidgetProviders 類處理,使得用戶程序可更新主 屏幕 Widget。

pendingIntent是一種特殊的 Intent。主要的區(qū)別在于 Intent 的執(zhí)行立刻的,而 pendingIntent 的執(zhí)行不是立刻的。本次使用方法類的靜態(tài)方法為 getActivity(Context, int, Intent, int),對應 Intent 的跳轉(zhuǎn)到一個 activity 組件的操作。
使用AppWidgetProvider類
你必須通過在清單文件中使用<receiver>元素來聲明你的AppWidgetProvider 類實現(xiàn)為一個廣播接收器(參見上面的Declaring an App Widget in the Manifest)。
AppWidgetProvider 類擴展BroadcastReceiver 為一個簡便類來處理App Widget廣播。AppWidgetProvider只接收和這個App Widget相關的事件廣播,比如這個App Widget被更新,刪除,啟用,以及禁用。當這些廣播事件發(fā)生時,AppWidgetProvider 將接收到下面的方法調(diào)用:
onUpdate(Context, AppWidgetManager, int[])
這個方法調(diào)用來間隔性的更新App Widget,間隔時間用AppWidgetProviderInfo 里的updatePeriodMillis屬性定義(參見添加AppWidgetProviderInfo元數(shù)據(jù))。這個方法也會在用戶添加App Widget時被調(diào)用,因此它應該執(zhí)行基礎的設置,比如為視圖定義事件處理器并啟動一個臨時的服務Service,如果需要的話。但是,如果你已經(jīng)聲明了一個配置活動,這個方法在用戶添加App Widget時將不會被調(diào)用,而只在后續(xù)更新時被調(diào)用。配置活動應該在配置完成時負責執(zhí)行第一次更新。(參見下面的創(chuàng)建一個App Widget配置活動Creating an App Widget Configuration Activity。)
onDeleted(Context, int[])
當App Widget從宿主中刪除時被調(diào)用。
onEnabled(Context)
當一個App Widget實例第一次創(chuàng)建時被調(diào)用。比如,如果用戶添加兩個你的App Widget實例,只在第一次被調(diào)用。如果你需要打開一個新的數(shù)據(jù)庫或者執(zhí)行其他對于所有的App Widget實例只需要發(fā)生一次的設置,那么這里是完成這個工作的好地方。
onDisabled(Context)
當你的App Widget的最后一個實例被從宿主中刪除時被調(diào)用。你應該在onEnabled(Context)中做一些清理工作,比如刪除一個臨時的數(shù)據(jù)庫。
onReceive(Context, Intent)
這個接收到每個廣播時都會被調(diào)用,而且在上面的回調(diào)函數(shù)之前。你通常不需要實現(xiàn)這個方法,因為缺省的AppWidgetProvider 實現(xiàn)過濾所有App Widget 廣播并恰當?shù)恼{(diào)用上述方法。
注意: 在Android 1.5中, 有一個已知問題,onDeleted()方法在該調(diào)用時不被調(diào)用。為了規(guī)避這個問題,你可以像Group post中描述的那樣實現(xiàn)onReceive() 來接收這個onDeleted()回調(diào)。
最重要的AppWidgetProvider 回調(diào)函數(shù)是onUpdated(), 因為它是在每個App Widget添加進宿主時被調(diào)用的(除非你使用一個配置活動)。如果你的App Widget 要接受任何用戶交互事件,那么你需要在這個回調(diào)函數(shù)中注冊事件處理器。如果你的App Widget不創(chuàng)建臨時文件或數(shù)據(jù)庫,或者執(zhí)行其它需要清理的工作,那么onUpdated() 可能是你需要定義的唯一的回調(diào)函數(shù)。
4.重寫 onReceive 方法
在 Widget 類中重寫 onReceive 方法,這里需要使用到 RemoteView 以及 Bundle。當接 收到對應廣播時進行數(shù)據(jù)處理。 
if 條件語句中主要用到的函數(shù)為:setTextViewText、setImageViewResource。 之后使用 AppWidgetManager 類對 Widget 進行更新。
實驗內(nèi)容
實現(xiàn)一個 Android 應用,實現(xiàn)靜態(tài)廣播、動態(tài)廣播兩種改變 widget 內(nèi)容的方法。在上次實 驗的基礎上進行修改,所以一些關于靜態(tài)動態(tài)廣播的內(nèi)容會簡略。
具體要求:
(1)該界面為應用啟動后看到的界面。

widget 初始情況如下

(2)點擊靜態(tài)注冊按鈕,跳轉(zhuǎn)至如下界面?! ?br />

點擊表單項目。如 banana。widget 會發(fā)生對應變化。點擊 Widget 上的圖片可以跳轉(zhuǎn)回主頁面
(3)點擊動態(tài)注冊按鈕,跳轉(zhuǎn)至如下界面。 實現(xiàn)以下功能:
a)可以編輯廣播的信息,點擊 Send 按鈕發(fā)送廣播。
b)設置一個按鈕進行廣播接收器的注冊與注銷。
c)廣播接收器若已被注冊,發(fā)送出的廣播信息能夠及時更新桌面上 Widget 上文字內(nèi)容及 更新為默認 dynamic 圖片。
d)點擊 Widget 上的圖片可以跳轉(zhuǎn)回主頁面。

實驗步驟
首先,在Android Studio中創(chuàng)建Widget類,直接生成相關文件,其中包括界面布局XML文件、widget的provider文件信息(xml)以及在項目的AndroidMenifest.xml文件中添加了一個receiver標簽,需要我們添加過濾更新事件,并需要指向之前創(chuàng)建的Widget類。
AndroidMenifest.xml文件中,intent-filter中過濾了APPWIDGET_UPDATE事件,這個事件是由系統(tǒng)觸發(fā)的更新事件,每個widget必須包含這個事件;meta-data標簽描述的是widget的配置文件指向,該文件描述了widget的一些基本信息(其中由于需要在靜態(tài)注冊中實現(xiàn),intent-filter中也過濾了staticreceiver):
<receiver
android:name=".MyAppWidget"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="com.example.yanglh6.myapplication4.staticreceiver" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/my_app_widget_info"/>
</receiver>
接下來根據(jù)要求編寫widget的provider文件信息(xml),minWidth和minHeight是widget的最小寬度和高度,這個值是一個參考值,系統(tǒng)會根據(jù)實際情況進行改變,initialLayout屬性指明widge的視圖布局文件,updatePeriodMillis屬性是widget每隔多久更新一次的時間,單位為毫秒:
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialKeyguardLayout="@layout/my_app_widget" android:initialLayout="@layout/my_app_widget" android:minHeight="55dp" android:minWidth="200dp" android:previewImage="@drawable/example_appwidget_preview" android:resizeMode="horizontal|vertical" android:updatePeriodMillis="86400000" android:widgetCategory="home_screen"></appwidget-provider>
接下來就是界面布局,在這個示例中需要一個ImageView控件和一個TextView控件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"> <ImageView android:id="@+id/WidgetImage" android:layout_width="60dp" android:layout_height="60dp" android:gravity="center" android:src="@mipmap/apple"/> <TextView android:id="@+id/WidgetName" android:layout_width="wrap_content" android:layout_height="60dp" android:textColor="@color/red" android:textSize="20dp" android:layout_toRightOf="@+id/WidgetImage" android:text="Apple" android:gravity="center"/> </RelativeLayout>
布局文件實現(xiàn)了一個如下圖的布局:

然后在Widget中,重寫onUpdate方法,為Widget添加事件,使得能夠返回主頁面。這里需要使用到一種用戶程序訪問主屏幕和修改特定區(qū)域內(nèi)容的方法RemoteView架構。RemoteView架構允許用戶程序更新主屏幕的View,點擊 Widget激活點擊事件,Android會將其轉(zhuǎn)發(fā)給用戶程序,由AppWidgetProviders類處理,使得用戶程序可更新主屏幕Widget。
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
Intent clickInt = new Intent(context, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, clickInt, 0);
RemoteViews view = new RemoteViews(context.getPackageName(),R.layout.my_app_widget);
view.setOnClickPendingIntent(R.id.WidgetImage, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetIds, view);
}
接下來在Widge類中重寫onReceive方法,這里需要使用到RemoteView以及Bundle。當接收到對應廣播時進行數(shù)據(jù)處理(由于我們在AndroidMenifest.xml文件中注冊時將APPWIDGET_UPDAT事件和staticreceiver都指向Widge類,所以在這里我們StaticReceiver類刪掉,將里面對OnReceive函數(shù)重寫的部分添加在Widget類中):
@Override
public void onReceive(Context context, Intent intent) {
Log.i("debug", intent.toString());
super.onReceive(context, intent);
RemoteViews view = new RemoteViews(context.getPackageName(),R.layout.my_app_widget);
Bundle bundle = intent.getExtras();
String widgetName = bundle.getString("name");
int widgetImage = bundle.getInt("ItemImage");
if (intent.getAction().equals("com.example.yanglh6.myapplication4.staticreceiver")) {
view.setTextViewText(R.id.WidgetName, widgetName);
view.setImageViewResource(R.id.WidgetImage, widgetImage);
AppWidgetManager appWidgetManager=AppWidgetManager.getInstance(context);
appWidgetManager.updateAppWidget(new ComponentName(context, MyAppWidget.class), view);
Bitmap bitmap= BitmapFactory.decodeResource(context.getResources(),bundle.getInt("ItemImage"));
int imageId = (int) bundle.get("ItemImage");
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification.Builder builder = new Notification.Builder(context);
builder.setContentTitle("靜態(tài)廣播")
.setContentText(bundle.getString("name"))
.setLargeIcon(bitmap)
.setSmallIcon(imageId)
.setTicker("您有一條新消息")
.setAutoCancel(true);
Intent Intent1 = new Intent(context, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, Intent1, 0);
builder.setContentIntent(pendingIntent);
Notification notify = builder.build();
notificationManager.notify(0, notify);
}
}
單獨把Widget部分onReceive方法的重寫列出:
public void onReceive(Context context, Intent intent) {
Log.i("debug", intent.toString());
super.onReceive(context, intent);
RemoteViews view = new RemoteViews(context.getPackageName(),R.layout.my_app_widget);
Bundle bundle = intent.getExtras();
String widgetName = bundle.getString("name");
int widgetImage = bundle.getInt("ItemImage");
if (intent.getAction().equals("com.example.yanglh6.myapplication4.staticreceiver")) {
view.setTextViewText(R.id.WidgetName, widgetName);
view.setImageViewResource(R.id.WidgetImage, widgetImage);
AppWidgetManager appWidgetManager=AppWidgetManager.getInstance(context);
appWidgetManager.updateAppWidget(new ComponentName(context, MyAppWidget.class), view);
}
}
對于動態(tài)注冊來說,不需要在AndroidMenifest.xml添加receiver,但在DynamicActivity中進行注冊:
dynamicReceiver = new DynamicReceiver();
IntentFilter dynamic_filter = new IntentFilter();
dynamic_filter.addAction("com.example.yanglh6.myapplication4.dynamicreceiver");
registerReceiver(dynamicReceiver, dynamic_filter);
所以動態(tài)注冊時只能在DynamicReceiver中對Onreceive函數(shù)進行重寫,完成Widget的更新(與靜態(tài)注冊類似):
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("com.example.yanglh6.myapplication4.dynamicreceiver")) {
Bundle bundle = intent.getExtras();
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), bundle.getInt("ItemImage"));
int imageId = bundle.getInt("ItemImage");
RemoteViews view = new RemoteViews(context.getPackageName(),R.layout.my_app_widget);
String widgetName = bundle.getString("name");
view.setTextViewText(R.id.WidgetName, widgetName);
view.setImageViewResource(R.id.WidgetImage, imageId);
AppWidgetManager appWidgetManager=AppWidgetManager.getInstance(context);
appWidgetManager.updateAppWidget(new ComponentName(context, MyAppWidget.class), view);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification.Builder builder = new Notification.Builder(context);
builder.setContentTitle("動態(tài)廣播")
.setContentText(widgetName)
.setLargeIcon(bitmap)
.setSmallIcon(imageId)
.setTicker("您有一條新消息")
.setAutoCancel(true);
Intent mIntent = new Intent(context, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, mIntent, 0);
builder.setContentIntent(pendingIntent);
Notification notify = builder.build();
notificationManager.notify(0, notify);
}
}
完成實驗~
運行截圖

注意事項
自己要充分理解AndroidMenifest.xml各部分的含義以及Android的機制,在AndroidMenifest.xml的注冊和指向必須清晰。
對于靜態(tài)來說,在sendBroadcast(intent)實現(xiàn)后,在AndroidMenifest.xml找到intent注冊時的receiver并指向?qū)膹V播接收函數(shù),在這個函數(shù)中實現(xiàn)各個事件;對于動態(tài)來說,由于在DynamicActivity中進行注冊,在那時可以定義指向的動態(tài)廣播接收類。
源碼下載
注
本實驗實驗環(huán)境:
操作系統(tǒng) Windows 10
實驗軟件 Android Studio 2.2.1
虛擬設備:Galaxy_Nexus
API:21
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
android 電話狀態(tài)監(jiān)聽(來電和去電)實現(xiàn)代碼
從事android開發(fā)的朋友們可能電話狀態(tài)監(jiān)聽不是很擅長,接下來將詳細介紹電話狀態(tài)監(jiān)聽功能的實現(xiàn)步驟,需要了解的朋友可以參考下2012-12-12

