Android RemoteViews的基本使用(下)之窗口小部件

一,寫在前面            

        在文章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的註冊     

       分析:AppWidgetProvider繼承了BroadcastReceiver,可以看出窗口小部件就是一個廣播接受者,因此在步驟2中需要對廣播進行註冊,查看官方文檔有這樣一個例子:

       可以看到我們配置了一個這樣的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,步驟同上。
窗口小部件展示如下:
點擊按鈕前


點擊按鈕後,如下:

三,另外

         在重寫的onReceive方法中,有這樣一行代碼:super.onReceive(context, intent),通過上面分析知道,它完成對窗口小部件生命週期的調度。不斷接受系統發送的廣播,所以onEnabled,onDisabled,onUpdate,onDeleted被調用前都會調用onReceive方法。
         驗證時,若在onReceive方法中添加Log,日誌信息如下:


四,最後

本篇文章介紹瞭如何實現一個簡單的窗口小部件,而,RemoteViews用於更新遠程進程中窗口小部件的界面。

這篇文章就分享到這裏啦,有疑問可以留言,亦可糾錯,亦可補充,互相學習...^_^ 







發佈了47 篇原創文章 · 獲贊 28 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章