Android RemoteViews的基本使用(下)之視窗小部件
阿新 • • 發佈:2019-02-19
一,寫在前面
在文章RemoteViews的基本使用(上)之通知欄 中講述了的RemoteViews使用場景之通知欄,這篇文章主要講述RemoteViews在視窗小部件中的使用。在寫好了一個視窗小部件之後,如果需要對小部件的介面進行更新,由於在本應用中無法呼叫findViewbyid(id)方法獲取控制元件引用(需要跨程序訪問介面),這個時候RemoteViews就派上用場了。在上篇文章中講到,RemoteViews可以實現跨程序更新介面,內部實現原理是Binder機制,後面會單獨更新一篇從原始碼角度分析RemoteViews。這篇文章主要介紹RemoteViews在視窗小部件的使用,並介紹視窗小部件的簡單實現。
二,視窗小部件
如何實現一個視窗小部件呢,大致需要這樣幾個步驟: 1,建立一個類MyAppWidgetProvider,並繼承AppWidgetProvider,並重寫方法:onReceiver,onEnabled,onDisabled,onUpdate,onDeleted等; 2,在AndroidManifest.xml檔案中對MyAppWidgetProvider進行配置,需要引入一個xml檔案,見步驟3; 3,在res目錄下,建立xml資料夾,並提編輯一個AppWidgetProviderInfo對應的資原始檔:my_appwidget_info.xml,;需要引用一個xml佈局,見步驟4; 4,建立一個layout的佈局檔案:my_appwidget.xml;AppWidgetProvider的註冊
可以看到我們配置了一個這樣的action:android.appwidget.action.APPWIDGET_UPDATE,官方文件有描述這個action是必須配置的。在實際測試之後發現,如果不新增該action,那麼在視窗小部件列表中找不到這個app widget。AppWidgetProvider可以接受其他的廣播,可以在Intent-filter中配置其他廣播action。 <mata-data>是指定AppWidgetProviderInfo對應的資原始檔,也就是步驟3中的my_appwidget_info.xml。 AndroidManifest.xml新增程式碼如下:
<receiver android:name="com.example.appwidgetdemo.MyAppWidgetProvider" > <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <!-- 註冊接受點選Button傳送的廣播 --> <action android:name="com.widget.wang"/> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/my_appwidget_info" /> </receiver>
AppWidgetProviderInfo對應資原始檔
附上my_appwidget_info.xml檔案的程式碼,如下:<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="180dp"
android:minHeight="110dp"
android:previewImage="@drawable/ic_launcher"
android:initialLayout="@layout/my_appwidget">
</appwidget-provider>
分析:在建立該資原始檔時,選擇Resource Type為AppWidget Provider,這樣會自動生成一個帶有appwidget-provider標籤的檔案,手寫亦可。
下面分析一些常用的屬性:
minWidth,minHeight:小部件的最小寬度,最小高度。它是一個約束值,小部件的具體寬高會根據裝置的網格單元來設定,它們一定會>=minWidth/minHeight。也就是說小部件的寬高大小的單位是網格單元,有些手機會提供4*4網格,平板提供8*7網格,網格與dp值的關係,見如下表格:
例如:minWidth設定為30dp,那麼系統會設定小部件寬度為一個單元格,大小為40dp; previewImage:視窗小部件列表中的預覽圖片;
initialLayout:初始化佈局,顯示在桌面上的佈局檔案;
當然,還有其他一些屬性,這裡就不一一介紹了。
layout佈局檔案
程式碼如下:<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="#f00"
android:layout_centerHorizontal="true"
android:text="TextView"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/tv"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@drawable/ttnkh"/>
<Button
android:id="@+id/btn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center"
android:text="Button"/>
</LinearLayout>
</RelativeLayout>
AppWidgetProvider的生命週期
先描述視窗小部件生命週期各個方法的呼叫時機: onReceive:接受系統傳送的廣播,用於排程onEnabled,onDisabled,onUpdate,onDeleted方法的呼叫; onEnabled:只在app widget第一次出現在桌面時呼叫;onDisabled:只在最後一個小部件被刪除時呼叫;
onUpdate:小部件每次新增到桌面,或小部件更新時呼叫;
onDeleted:部件從桌面刪除時呼叫,刪除一次,呼叫一次;
接下來,檢視AppWidgetProvider$onReceive原始碼,檢視其工作機制:
public void onReceive(Context context, Intent intent) {
// Protect against rogue update broadcasts (not really a security issue,
// just filter bad broacasts out so subclasses are less likely to crash).
String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds != null && appWidgetIds.length > 0) {
this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
}
}
}
else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
this.onDeleted(context, new int[] { appWidgetId });
}
}
else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)
&& extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {
int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),
appWidgetId, widgetExtras);
}
}
else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
this.onEnabled(context);
}
else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
this.onDisabled(context);
}
}
當系統傳送廣播時,AppWidgetProvider可以接受廣播,並對廣播中的action進行判斷,分別呼叫onEnabled,onDisabled,onUpdate,onDeleted等方法。檢視原始碼可知,這些方法的方法體都是空的,具體的實現需要子類重寫咯。
MyAppWidgetProvider程式碼如下:public class MyAppWidgetProvider extends AppWidgetProvider {
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if ("com.widget.wang".equals(intent.getAction())) {
//接受點選按鈕後傳送的廣播,將RemoteView的圖片設定為R.drawable.ic_launcher
RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.my_appwidget);
rv.setImageViewResource(R.id.iv, R.drawable.ic_launcher);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
ComponentName provider = new ComponentName(context, MyAppWidgetProvider.class);
appWidgetManager.updateAppWidget(provider, rv);
}
}
@Override
public void onEnabled(Context context) {
super.onEnabled(context);
Log.e("MyAppWidgetProvider", "call onEnabled");
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
Log.e("MyAppWidgetProvider", "call onUpdate");
RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.my_appwidget);
rv.setTextViewText(R.id.tv, "更新視窗小部件介面");
rv.setTextColor(R.id.tv, Color.WHITE);
Intent intent = new Intent();
intent.setAction("com.widget.wang");
PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// 給Button設定點選事件,觸發一個Intent,這裡是傳送廣播
rv.setOnClickPendingIntent(R.id.btn, pi);
appWidgetManager.updateAppWidget(appWidgetIds, rv);
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
Log.e("MyAppWidgetProvider", "call onDeleted");
}
@Override
public void onDisabled(Context context) {
super.onDisabled(context);
Log.e("MyAppWidgetProvider", "call onDisabled");
}
}
驗證視窗小部件的生命週期方法的呼叫,執行這樣一些操作:新增小部件到桌面 -> 新增小部件到桌面->刪除小部件->刪除小部件。檢視日誌如下:
現在,介紹一下該Demo具體實現的一個簡單需求:小部件新增到桌面時,設定TextView控制元件的文字內容,文字顏色,並給Button設定點選事件。 通過上面簡單分析可知,在onUpdate方法中實現上面需求。若想跨程序更新視窗小部件的介面,有這樣一些步驟: 1,需建立一個RemoteViews物件,構造方法引數傳入包名,以及佈局資源id; 2,對RemoteViews中控制元件進行更新,以及設定控制元件的點選事件; 3,獲取一個AppWidgetManager物件,呼叫updateAppWidget(...)方法更新視窗小部件,該方法有一個引數需要傳入步驟1中的RemoteViews的例項; 傳送廣播 點選按鈕後,傳送一個action為"com.widget.wang"的廣播,具體實現見上面程式碼。
接受廣播:設定視窗小部件中的ImageView控制元件的圖片資源
MyAppWidgetProvider接受廣播,需要在AndroidManifest.xml對廣播進行註冊,並在onReceive方法中接受廣播並處理。接受action為"com.widget.wang"的廣播,更新視窗小部件的介面需要使用RemoteViews,步驟同上。視窗小部件展示如下:
點選按鈕前點選按鈕後,如下: