Android開發(fā)5:應(yīng)用程序窗口小部件App Widgets的實(shí)現(xiàn)(附demo)
前言
本次主要是實(shí)現(xiàn)一個(gè)Android應(yīng)用,實(shí)現(xiàn)靜態(tài)廣播、動(dòng)態(tài)廣播兩種改變 widget內(nèi)容的方法,即在上篇博文中實(shí)驗(yàn)的基礎(chǔ)上進(jìn)行修改,所以此次實(shí)驗(yàn)的重點(diǎn)是AppWidget小部件的實(shí)現(xiàn)啦~
首先,我們簡單說一下Widget是一個(gè)啥玩意~
應(yīng)用程序窗口小部件(Widget)是微小的應(yīng)用程序視圖,可以被嵌入到其它應(yīng)用程序中(比如桌面)并接收周期性的更新。你可以通過一個(gè)App Widget provider來發(fā)布一個(gè)Widget??梢匀菁{其它App Widget的應(yīng)用程序組件被稱為App Widget宿主。
Widget是在桌面上的一塊顯示信息的東西,也通過單擊Widget跳轉(zhuǎn)到一個(gè)程序里面。而系統(tǒng)自帶的程序,典型的Widget是music,這個(gè)Android內(nèi)置的音樂播放小程序。這個(gè)是典型的Widget+app應(yīng)用。就是一個(gè)程序既可以通過Widget啟動(dòng),也可以通過App啟動(dòng)。Widget就是一個(gè)AppWidgetProvider+一個(gè)UI界面顯示(預(yù)先綁定了好多Intent),界面上的信息可以通過程序控制而改變,單擊Widget,上的控件只能激發(fā)發(fā)送一個(gè)Intent,或發(fā)出一個(gè)Service的啟動(dòng)通知。而AppWidgetProvider可以攔截這個(gè)Intent,而進(jìn)行相應(yīng)的處理(比如顯示新的信息)。
基礎(chǔ)知識(shí)
為了創(chuàng)建一個(gè)App Widget,你需要下面這些:
AppWidgetProviderInfo 對(duì)象
描述一個(gè)App Widget元數(shù)據(jù),比如App Widget的布局,更新頻率,以及AppWidgetProvider 類。這應(yīng)該在XML里定義。
AppWidgetProvider 類的實(shí)現(xiàn)
定義基本方法以允許你編程來和App Widget連接,這基于廣播事件。通過它,當(dāng)這個(gè)App Widget被更新,啟用,禁用和刪除的時(shí)候,你都將接收到廣播通知。
視圖布局
為這個(gè)App Widget定義初始布局,在XML中。
另外,你可以實(shí)現(xiàn)一個(gè)App Widget配置活動(dòng)。這是一個(gè)可選的活動(dòng)Activity,當(dāng)用戶添加App Widget時(shí)加載并允許他在創(chuàng)建時(shí)來修改App Widget的設(shè)置。
widget 的添加:長按菜單鍵,點(diǎn)擊 widgets 選項(xiàng)。找到對(duì)應(yīng)的 widget 將其拖入桌面。對(duì) 于不同的 API 版本顯示會(huì)稍有不同。
典型的 Android Widget 有三個(gè)主要組件,一個(gè)邊框、一個(gè)框架和圖形控件以及其他元素。 在 Android Studio 中創(chuàng)建 Widget 類后,會(huì)直接生成相關(guān)文件。
首先,在應(yīng)用程序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> 元素必須包括一個(gè)含有android:name屬性的<action>元素。該元素指定AppWidgetProvider接受ACTION_APPWIDGET_UPDATE 廣播。這是唯一你必須顯式聲明的廣播。當(dāng)需要的時(shí)候,AppWidgetManager 會(huì)自動(dòng)發(fā)送所有其他App Widget廣播給AppWidgetProvider。
<meta-data> 元素指定了AppWidgetProviderInfo 資源并需要以下屬性:
- android:name – 指定元數(shù)據(jù)名稱。
- android:resource – 指定AppWidgetProviderInfo 資源路徑。
1. Widget 布局文件 widget_demo.xml,布局中有一個(gè) ImageView,一個(gè) TextView。 要求:文字顏色為紅色,大小為 20dp,整體背景為透明。最后效果如下:
2.增加AppWidgetProviderInfo元數(shù)據(jù)
AppWidgetProviderInfo定義一個(gè)App Widget的基本特性,比如最小布局尺寸,初始布局資源,刷新頻率,以及(可選的)創(chuàng)建時(shí)加載的一個(gè)配置活動(dòng)。使用單獨(dú)的一個(gè)<appwidget-provider>元素在XML資源里定義AppWidgetProviderInfo 對(duì)象并保存到項(xiàng)目的res/xml/目錄下。
Widget 內(nèi)容提供者文件 widget_demo_info.xml,編輯該文件,設(shè)置其大小屬性和布 局,如下圖:
其中,minWidth 為最小寬度,minHeight 為最小高度,initialLayout 為初始布局。
3. 修改 WidgetDemo.java 代碼,重寫 onUpdate 方法,為 Widget 添加事件,使得能夠返 回主頁面。
這里需要使用到一種用戶程序訪問主屏幕和修改特定區(qū)域內(nèi)容的方法:RemoteView 架 構(gòu) 。RemoteView 架構(gòu)允許用戶程序更新主屏幕的 View,點(diǎn)擊 Widget 激活點(diǎn)擊事件,Android 會(huì)將其轉(zhuǎn)發(fā)給用戶程序,由 AppWidgetProviders 類處理,使得用戶程序可更新主 屏幕 Widget。
pendingIntent是一種特殊的 Intent。主要的區(qū)別在于 Intent 的執(zhí)行立刻的,而 pendingIntent 的執(zhí)行不是立刻的。本次使用方法類的靜態(tài)方法為 getActivity(Context, int, Intent, int),對(duì)應(yīng) Intent 的跳轉(zhuǎn)到一個(gè) activity 組件的操作。
使用AppWidgetProvider類
你必須通過在清單文件中使用<receiver>元素來聲明你的AppWidgetProvider 類實(shí)現(xiàn)為一個(gè)廣播接收器(參見上面的Declaring an App Widget in the Manifest)。
AppWidgetProvider 類擴(kuò)展BroadcastReceiver 為一個(gè)簡便類來處理App Widget廣播。AppWidgetProvider只接收和這個(gè)App Widget相關(guān)的事件廣播,比如這個(gè)App Widget被更新,刪除,啟用,以及禁用。當(dāng)這些廣播事件發(fā)生時(shí),AppWidgetProvider 將接收到下面的方法調(diào)用:
onUpdate(Context, AppWidgetManager, int[])
這個(gè)方法調(diào)用來間隔性的更新App Widget,間隔時(shí)間用AppWidgetProviderInfo 里的updatePeriodMillis屬性定義(參見添加AppWidgetProviderInfo元數(shù)據(jù))。這個(gè)方法也會(huì)在用戶添加App Widget時(shí)被調(diào)用,因此它應(yīng)該執(zhí)行基礎(chǔ)的設(shè)置,比如為視圖定義事件處理器并啟動(dòng)一個(gè)臨時(shí)的服務(wù)Service,如果需要的話。但是,如果你已經(jīng)聲明了一個(gè)配置活動(dòng),這個(gè)方法在用戶添加App Widget時(shí)將不會(huì)被調(diào)用,而只在后續(xù)更新時(shí)被調(diào)用。配置活動(dòng)應(yīng)該在配置完成時(shí)負(fù)責(zé)執(zhí)行第一次更新。(參見下面的創(chuàng)建一個(gè)App Widget配置活動(dòng)Creating an App Widget Configuration Activity。)
onDeleted(Context, int[])
當(dāng)App Widget從宿主中刪除時(shí)被調(diào)用。
onEnabled(Context)
當(dāng)一個(gè)App Widget實(shí)例第一次創(chuàng)建時(shí)被調(diào)用。比如,如果用戶添加兩個(gè)你的App Widget實(shí)例,只在第一次被調(diào)用。如果你需要打開一個(gè)新的數(shù)據(jù)庫或者執(zhí)行其他對(duì)于所有的App Widget實(shí)例只需要發(fā)生一次的設(shè)置,那么這里是完成這個(gè)工作的好地方。
onDisabled(Context)
當(dāng)你的App Widget的最后一個(gè)實(shí)例被從宿主中刪除時(shí)被調(diào)用。你應(yīng)該在onEnabled(Context)中做一些清理工作,比如刪除一個(gè)臨時(shí)的數(shù)據(jù)庫。
onReceive(Context, Intent)
這個(gè)接收到每個(gè)廣播時(shí)都會(huì)被調(diào)用,而且在上面的回調(diào)函數(shù)之前。你通常不需要實(shí)現(xiàn)這個(gè)方法,因?yàn)槿笔〉腁ppWidgetProvider 實(shí)現(xiàn)過濾所有App Widget 廣播并恰當(dāng)?shù)恼{(diào)用上述方法。
注意: 在Android 1.5中, 有一個(gè)已知問題,onDeleted()方法在該調(diào)用時(shí)不被調(diào)用。為了規(guī)避這個(gè)問題,你可以像Group post中描述的那樣實(shí)現(xiàn)onReceive() 來接收這個(gè)onDeleted()回調(diào)。
最重要的AppWidgetProvider 回調(diào)函數(shù)是onUpdated(), 因?yàn)樗窃诿總€(gè)App Widget添加進(jìn)宿主時(shí)被調(diào)用的(除非你使用一個(gè)配置活動(dòng))。如果你的App Widget 要接受任何用戶交互事件,那么你需要在這個(gè)回調(diào)函數(shù)中注冊(cè)事件處理器。如果你的App Widget不創(chuàng)建臨時(shí)文件或數(shù)據(jù)庫,或者執(zhí)行其它需要清理的工作,那么onUpdated() 可能是你需要定義的唯一的回調(diào)函數(shù)。
4.重寫 onReceive 方法
在 Widget 類中重寫 onReceive 方法,這里需要使用到 RemoteView 以及 Bundle。當(dāng)接 收到對(duì)應(yīng)廣播時(shí)進(jìn)行數(shù)據(jù)處理。
if 條件語句中主要用到的函數(shù)為:setTextViewText、setImageViewResource。 之后使用 AppWidgetManager 類對(duì) Widget 進(jìn)行更新。
實(shí)驗(yàn)內(nèi)容
實(shí)現(xiàn)一個(gè) Android 應(yīng)用,實(shí)現(xiàn)靜態(tài)廣播、動(dòng)態(tài)廣播兩種改變 widget 內(nèi)容的方法。在上次實(shí) 驗(yàn)的基礎(chǔ)上進(jìn)行修改,所以一些關(guān)于靜態(tài)動(dòng)態(tài)廣播的內(nèi)容會(huì)簡略。
具體要求:
(1)該界面為應(yīng)用啟動(dòng)后看到的界面。
widget 初始情況如下
(2)點(diǎn)擊靜態(tài)注冊(cè)按鈕,跳轉(zhuǎn)至如下界面?! ?br />
點(diǎn)擊表單項(xiàng)目。如 banana。widget 會(huì)發(fā)生對(duì)應(yīng)變化。點(diǎn)擊 Widget 上的圖片可以跳轉(zhuǎn)回主頁面
(3)點(diǎn)擊動(dòng)態(tài)注冊(cè)按鈕,跳轉(zhuǎn)至如下界面。 實(shí)現(xiàn)以下功能:
a)可以編輯廣播的信息,點(diǎn)擊 Send 按鈕發(fā)送廣播。
b)設(shè)置一個(gè)按鈕進(jìn)行廣播接收器的注冊(cè)與注銷。
c)廣播接收器若已被注冊(cè),發(fā)送出的廣播信息能夠及時(shí)更新桌面上 Widget 上文字內(nèi)容及 更新為默認(rèn) dynamic 圖片。
d)點(diǎn)擊 Widget 上的圖片可以跳轉(zhuǎn)回主頁面。
實(shí)驗(yàn)步驟
首先,在Android Studio中創(chuàng)建Widget類,直接生成相關(guān)文件,其中包括界面布局XML文件、widget的provider文件信息(xml)以及在項(xiàng)目的AndroidMenifest.xml文件中添加了一個(gè)receiver標(biāo)簽,需要我們添加過濾更新事件,并需要指向之前創(chuàng)建的Widget類。
AndroidMenifest.xml文件中,intent-filter中過濾了APPWIDGET_UPDATE事件,這個(gè)事件是由系統(tǒng)觸發(fā)的更新事件,每個(gè)widget必須包含這個(gè)事件;meta-data標(biāo)簽描述的是widget的配置文件指向,該文件描述了widget的一些基本信息(其中由于需要在靜態(tài)注冊(cè)中實(shí)現(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的最小寬度和高度,這個(gè)值是一個(gè)參考值,系統(tǒng)會(huì)根據(jù)實(shí)際情況進(jìn)行改變,initialLayout屬性指明widge的視圖布局文件,updatePeriodMillis屬性是widget每隔多久更新一次的時(shí)間,單位為毫秒:
<?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>
接下來就是界面布局,在這個(gè)示例中需要一個(gè)ImageView控件和一個(gè)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>
布局文件實(shí)現(xiàn)了一個(gè)如下圖的布局:
然后在Widget中,重寫onUpdate方法,為Widget添加事件,使得能夠返回主頁面。這里需要使用到一種用戶程序訪問主屏幕和修改特定區(qū)域內(nèi)容的方法RemoteView架構(gòu)。RemoteView架構(gòu)允許用戶程序更新主屏幕的View,點(diǎn)擊 Widget激活點(diǎn)擊事件,Android會(huì)將其轉(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。當(dāng)接收到對(duì)應(yīng)廣播時(shí)進(jìn)行數(shù)據(jù)處理(由于我們?cè)贏ndroidMenifest.xml文件中注冊(cè)時(shí)將APPWIDGET_UPDAT事件和staticreceiver都指向Widge類,所以在這里我們StaticReceiver類刪掉,將里面對(duì)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); } }
單獨(dú)把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); } }
對(duì)于動(dòng)態(tài)注冊(cè)來說,不需要在AndroidMenifest.xml添加receiver,但在DynamicActivity中進(jìn)行注冊(cè):
dynamicReceiver = new DynamicReceiver(); IntentFilter dynamic_filter = new IntentFilter(); dynamic_filter.addAction("com.example.yanglh6.myapplication4.dynamicreceiver"); registerReceiver(dynamicReceiver, dynamic_filter);
所以動(dòng)態(tài)注冊(cè)時(shí)只能在DynamicReceiver中對(duì)Onreceive函數(shù)進(jìn)行重寫,完成Widget的更新(與靜態(tài)注冊(cè)類似):
@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("動(dòng)態(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); } }
完成實(shí)驗(yàn)~
運(yùn)行截圖
注意事項(xiàng)
自己要充分理解AndroidMenifest.xml各部分的含義以及Android的機(jī)制,在AndroidMenifest.xml的注冊(cè)和指向必須清晰。
對(duì)于靜態(tài)來說,在sendBroadcast(intent)實(shí)現(xiàn)后,在AndroidMenifest.xml找到intent注冊(cè)時(shí)的receiver并指向?qū)?yīng)的廣播接收函數(shù),在這個(gè)函數(shù)中實(shí)現(xiàn)各個(gè)事件;對(duì)于動(dòng)態(tài)來說,由于在DynamicActivity中進(jìn)行注冊(cè),在那時(shí)可以定義指向的動(dòng)態(tài)廣播接收類。
源碼下載
注
本實(shí)驗(yàn)實(shí)驗(yàn)環(huán)境:
操作系統(tǒng) Windows 10
實(shí)驗(yàn)軟件 Android Studio 2.2.1
虛擬設(shè)備:Galaxy_Nexus
API:21
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
android 電話狀態(tài)監(jiān)聽(來電和去電)實(shí)現(xiàn)代碼
從事android開發(fā)的朋友們可能電話狀態(tài)監(jiān)聽不是很擅長,接下來將詳細(xì)介紹電話狀態(tài)監(jiān)聽功能的實(shí)現(xiàn)步驟,需要了解的朋友可以參考下2012-12-12Android 自定義組件成JAR包的實(shí)現(xiàn)方法
這篇文章主要介紹了Android 自定義組件成JAR包的實(shí)現(xiàn)方法的相關(guān)資料,偶爾會(huì)用到這樣的功能,如果你自己自定義的組件很好,需要的朋友可以參考下2016-11-11Android組合控件實(shí)現(xiàn)功能強(qiáng)大的自定義控件
這篇文章主要介紹了Android組合控件實(shí)現(xiàn)功能強(qiáng)大的自定義控件的相關(guān)資料,需要的朋友可以參考下2016-05-05