Android開發5:應用程式視窗小組件App Widgets的實現,androidwidgets
前言
本次主要是實現一個Android應用,實現靜態廣播、動態廣播兩種改變 widget內容的方法,即在上篇博文中實驗的基礎上進行修改,所以此次實驗的重點是AppWidget小組件的實現啦~
首先,我們簡單說一下Widget是一個啥玩意~
應用程式視窗小組件(Widget)是微小的應用程式視圖,可以被嵌入到其它應用程式中(比如案頭)並接收周期性的更新。你可以通過一個App Widget provider來發布一個Widget。可以容納其它App Widget的應用程式組件被稱為App Widget宿主。
Widget是在案頭上的一塊顯示資訊的東西,也通過單擊Widget跳轉到一個程式裡面。而系統內建的程式,典型的Widget是music,這個Android內建的音樂播放小程式。這個是典型的Widget+app應用。就是一個程式既可以通過Widget啟動,也可以通過App啟動。Widget就是一個AppWidgetProvider+一個UI介面顯示(預先綁定了好多Intent),介面上的資訊可以通過程式控制而改變,單擊Widget,上的控制項只能激發發送一個Intent,或發出一個Service的啟動通知。而AppWidgetProvider可以攔截這個Intent,而進行相應的處理(比如顯示新的資訊)。
基礎知識
為了建立一個App Widget,你需要下面這些:
AppWidgetProviderInfo 對象
描述一個App Widget中繼資料,比如App Widget的布局,更新頻率,以及AppWidgetProvider 類。這應該在XML裡定義。
AppWidgetProvider 類的實現
定義基本方法以允許你編程來和App Widget串連,這基於廣播事件。通過它,當這個App Widget被更新,啟用,禁用和刪除的時候,你都將接收到廣播通知。
視圖布局
為這個App Widget定義初始布局,在XML中。
另外,你可以實現一個App Widget配置活動。這是一個可選的活動Activity,當使用者添加App Widget時載入並允許他在建立時來修改App Widget的設定。
widget 的添加:長按菜單鍵,點擊 widgets 選項。找到對應的 widget 將其拖入案頭。對 於不同的 API 版本顯示會稍有不同。
典型的 Android Widget 有三個主要組件,一個邊框、一個架構和圖形控制項以及其他元素。 在 Android Studio 中建立 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 會自動發送所有其他App Widget廣播給AppWidgetProvider。
<meta-data> 元素指定了AppWidgetProviderInfo 資源並需要以下屬性:
-
-
- android:name – 指定中繼資料名稱。
- android:resource – 指定AppWidgetProviderInfo 資源路徑。
1. Widget 布局檔案 widget_demo.xml,布局中有一個 ImageView,一個 TextView。 要求:文字顏色為紅色,大小為 20dp,整體背景為透明。最後效果如下:
2.增加AppWidgetProviderInfo中繼資料
AppWidgetProviderInfo定義一個App Widget的基本特性,比如最小布局尺寸,初始布局資源,重新整理頻率,以及(可選的)建立時載入的一個配置活動。使用單獨的一個<appwidget-provider>元素在XML資源裡定義AppWidgetProviderInfo 對象並儲存到項目的res/xml/目錄下。
Widget 內容提供者檔案 widget_demo_info.xml,編輯該檔案,設定其大小屬性和布 局,如:
其中,minWidth 為最小寬度,minHeight 為最小高度,initialLayout 為初始布局。
3. 修改 WidgetDemo.java 代碼,重寫 onUpdate 方法,為 Widget 添加事件,使得能夠返 回首頁面。
這裡需要使用到一種使用者程式訪問主畫面和修改特定地區內容的方法:RemoteView 架 構 。RemoteView 架構允許使用者程式更新主畫面的 View,點擊 Widget 啟用點擊事件,Android 會將其轉寄給使用者程式,由 AppWidgetProviders 類處理,使得使用者程式可更新主 螢幕 Widget。
pendingIntent是一種特殊的 Intent。主要的區別在於 Intent 的執行立刻的,而 pendingIntent 的執行不是立刻的。本次使用方法類的靜態方法為 getActivity(Context, int, Intent, int),對應 Intent 的跳轉到一個 activity 組件的操作。
使用AppWidgetProvider類
你必須通過在資訊清單檔中使用<receiver>元素來聲明你的AppWidgetProvider 類實現為一個廣播接收器(參見上面的Declaring an App Widget in the Manifest)。
AppWidgetProvider 類擴充BroadcastReceiver 為一個簡便類來處理App Widget廣播。AppWidgetProvider只接收和這個App Widget相關的事件廣播,比如這個App Widget被更新,刪除,啟用,以及禁用。當這些廣播事件發生時,AppWidgetProvider 將接收到下面的方法調用:
onUpdate(Context, AppWidgetManager, int[])
這個方法調用來間隔性的更新App Widget,間隔時間用AppWidgetProviderInfo 裡的updatePeriodMillis屬性定義(參見添加AppWidgetProviderInfo中繼資料)。這個方法也會在使用者添加App Widget時被調用,因此它應該執行基礎的設定,比如為視圖定義事件處理器並啟動一個臨時的服務Service,如果需要的話。但是,如果你已經聲明了一個配置活動,這個方法在使用者添加App Widget時將不會被調用,而只在後續更新時被調用。配置活動應該在配置完成時負責執行第一次更新。(參見下面的建立一個App Widget配置活動Creating an App Widget Configuration Activity。)
onDeleted(Context, int[])
當App Widget從宿主中刪除時被調用。
onEnabled(Context)
當一個App Widget執行個體第一次建立時被調用。比如,如果使用者添加兩個你的App Widget執行個體,只在第一次被調用。如果你需要開啟一個新的資料庫或者執行其他對於所有的App Widget執行個體只需要發生一次的設定,那麼這裡是完成這個工作的好地方。
onDisabled(Context)
當你的App Widget的最後一個執行個體被從宿主中刪除時被調用。你應該在onEnabled(Context)中做一些清理工作,比如刪除一個臨時的資料庫。
onReceive(Context, Intent)
這個接收到每個廣播時都會被調用,而且在上面的回呼函數之前。你通常不需要實現這個方法,因為預設的AppWidgetProvider 實現過濾所有App Widget 廣播並恰當的調用上述方法。
注意: 在Android 1.5中, 有一個已知問題,onDeleted()方法在該調用時不被調用。為了規避這個問題,你可以像Group post中描述的那樣實現onReceive() 來接收這個onDeleted()回調。
最重要的AppWidgetProvider 回呼函數是onUpdated(), 因為它是在每個App Widget添加進宿主時被調用的(除非你使用一個配置活動)。如果你的App Widget 要接受任何使用者互動事件,那麼你需要在這個回呼函數中註冊事件處理器。如果你的App Widget不建立臨時檔案或資料庫,或者執行其它需要清理的工作,那麼onUpdated() 可能是你需要定義的唯一的回呼函數。
4.重寫 onReceive 方法
在 Widget 類中重寫 onReceive 方法,這裡需要使用到 RemoteView 以及 Bundle。當接 收到對應廣播時進行資料處理。
if 條件陳述式中主要用到的函數為:setTextViewText、setImageViewResource。 之後使用 AppWidgetManager 類對 Widget 進行更新。
實驗內容
實現一個 Android 應用,實現靜態廣播、動態廣播兩種改變 widget 內容的方法。在上次實 驗的基礎上進行修改,所以一些關於靜態動態廣播的內容會簡略。
具體要求:
(1)該介面為應用啟動後看到的介面。
widget 初始情況如下
(2)點擊靜態註冊按鈕,跳轉至如下介面。
點擊表單項目。如 banana。widget 會發生對應變化。點擊 Widget 上的圖片可以跳回首頁面
(3)點擊動態註冊按鈕,跳轉至如下介面。 實現以下功能:
a)可以編輯廣播的資訊,點擊 Send 按鈕發送廣播。
b)設定一個按鈕進行廣播接收器的註冊與登出。
c)廣播接收器若已被註冊,發送出的廣播資訊能夠及時更新案頭上 Widget 上文字內容及 更新為預設 dynamic 圖片。
d)點擊 Widget 上的圖片可以跳回首頁面。
實驗步驟
首先,在Android Studio中建立Widget類,直接產生相關檔案,其中包括介面布局XML檔案、widget的provider檔案資訊(xml)以及在項目的AndroidMenifest.xml檔案中添加了一個receiver標籤,需要我們添加過濾更新事件,並需要指向之前建立的Widget類。
AndroidMenifest.xml檔案中,intent-filter中過濾了APPWIDGET_UPDATE事件,這個事件是由系統觸發的更新事件,每個widget必須包含這個事件;meta-data標籤描述的是widget的設定檔指向,該檔案描述了widget的一些基本資料(其中由於需要在靜態註冊中實現,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>
接下來根據要求編寫widget的provider檔案資訊(xml),minWidth和minHeight是widget的最小寬度和高度,這個值是一個參考值,系統會根據實際情況進行改變,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>
布局檔案實現了一個如的布局:
然後在Widget中,重寫onUpdate方法,為Widget添加事件,使得能夠返回首頁面。這裡需要使用到一種使用者程式訪問主畫面和修改特定地區內容的方法RemoteView架構。RemoteView架構允許使用者程式更新主畫面的View,點擊 Widget啟用點擊事件,Android會將其轉寄給使用者程式,由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。當接收到對應廣播時進行資料處理(由於我們在AndroidMenifest.xml檔案中註冊時將APPWIDGET_UPDAT事件和staticreceiver都指向Widge類,所以在這裡我們StaticReceiver類刪掉,將裡面對OnReceive函數重寫的部分添加在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("靜態廣播") .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); } }
對於動態註冊來說,不需要在AndroidMenifest.xml添加receiver,但在DynamicActivity中進行註冊:
dynamicReceiver = new DynamicReceiver(); IntentFilter dynamic_filter = new IntentFilter(); dynamic_filter.addAction("com.example.yanglh6.myapplication4.dynamicreceiver"); registerReceiver(dynamicReceiver, dynamic_filter);
所以動態註冊時只能在DynamicReceiver中對Onreceive函數進行重寫,完成Widget的更新(與靜態註冊類似):
@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("動態廣播") .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的註冊和指向必須清晰。
對於靜態來說,在sendBroadcast(intent)實現後,在AndroidMenifest.xml找到intent註冊時的receiver並指向對應的廣播接收函數,在這個函數中實現各個事件;對於動態來說,由於在DynamicActivity中進行註冊,在那時可以定義指向的動態廣播接收類。
注
1、本實驗實驗環境:
作業系統 Windows 10
實驗軟體 Android Studio 2.2.1
虛擬設備:Galaxy_Nexus
API:21
2、貼代碼的時候由於插入代碼框的大小問題,代碼格式不太嚴整,望見諒~
謝謝大家~