桌面widget詳解(三)——桌面widget中的控件交互方法

這篇我們依然撇開播放器,講一講桌面widget中最基本的功能,大家先把最基本的給弄懂了,下篇再來實現播放器的桌面widget,可見實現一個複雜的桌面widget是多麼的困難。

先看下這篇的實現效果:

在widget中實現一個text和兩個button,當點擊第一個button的時候,text中顯示一個隨機數。

在上一篇中《桌面widget詳解(一)——基本demo構建》,我們簡單介紹了怎麼顯示了widget,但如何讓widget中的按鈕得到交互等問題還沒有涉及,這篇中,雖然是新開的佈局,但有關顯示的部分,我就不再細講,大家可以參考上一篇。

一、widget佈局

本來我不打算列出這部分內容,但考慮到這個佈局是新的,所以還是給大家簡單列一下吧。但有關xml下的<appwidget-provider和AndroidManifest.xml下的註冊就不再講了。不過,爲了區分,我把醜小孩的頭像改成了小貓咪。其它沒變化,看代碼就知道了,下面列出widget的佈局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#33000000"
    android:orientation="vertical" >
 
    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="5dip"
        android:layout_marginTop="30dip"
        android:gravity="center_horizontal"
        android:text="text"
        android:textSize="14sp" />
 
    <Button
        android:id="@+id/btn1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="btn_1" />
 
    <Button
        android:id="@+id/btn2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dip"
        android:text="btn2" />
 
</LinearLayout>

這個佈局很簡單,一個textView和兩個btn,沒什麼難度。

 

二、AppWidget中控件交互(ExampleAppWidgetProvider.java)

1、發送廣播與按鈕事件綁定

因爲appwidget運行的進程和我們創建的應用不在一個進程中,所以我們也就不能像平常引用控件那樣來獲得控件的實例。這時候,我們就要靠RemoteViews,直譯成中文應該是遠程視圖; 也就是說通過這個東西我們能夠獲得不在同一進程中的對象,這也就爲我們編寫appwidget的處理事件提供了幫助。我們使用一下代碼來創建一個RemoteViews。

RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
 
// 綁定事件
remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent);

第一句創建了一個remoteViews實例,然後將其中的按鈕與點擊事件綁定,後面的參數中又出來了一個PendingIntent。下面看看PendingIntent又是個什麼玩意。

intent英文意思是意圖,pending表示即將發生或來臨的事情。
PendingIntent這個類用於處理即將發生的事情。比如在通知Notification中用於跳轉頁面,但不是馬上跳轉。所以我們可以將它理解成一個封裝成消息的intent的。即這個intent並不是立即start,而是像消息一樣被髮送出去,等接收方接到以後,再分析裏面的內容。
 

Intent intent = new Intent();
intent.setClass(context, ExampleAppWidgetProvider.class);
intent.setData(Uri.parse("harvic:" + R.id.btn1));
 
// 設置pendingIntent的作用
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent, 0);

可以看到,PendingIntent是Intent的封裝,在構造PendingIntent前,也要先構造一個Intent,並可以利用Intent的屬性傳進去action,Extra等,同樣,在接收時,對方依然是接收Intent的,而不是接收PaddingIntent。這個問題,我們後面可以看到。
PendingIntent.getBroadcast(context, 0,intent, 0);指從系統中獲取一個用於可以發送BroadcastReceiver廣播的PendingIntent對象。

 

講完這兩個之後,我們看一下OnUpdate的內容:

其中:

String broadCastString = "harvic.provider";
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
		int[] appWidgetIds) {
 
	Intent intent = new Intent();
	intent.setClass(context, ExampleAppWidgetProvider.class);
	intent.setData(Uri.parse("harvic:" + R.id.btn1));
	
	// 設置pendingIntent的作用
	PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent, 0);
	RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
	remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent);
	// 更新Appwidget
	appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
}

這裏的總體步驟就是,構造一個RemoteView,然後利用updateAppWidget()將構造的RemoteView更新指定的widget界面。

先看構造pendingIntent的過程:

Intent intent = new Intent();
intent.setClass(context, ExampleAppWidgetProvider.class);
intent.setData(Uri.parse("harvic:" + R.id.btn1));
 
// 設置pendingIntent的作用
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent, 0);

首先是構造一個廣播時發送的intent,注意這個intent構造的是顯示intent,直接將這個廣播發送給ExampleAppWidgetProvider,附帶的數據通過Data傳送(最後附錄中會講爲什麼不通過putExtra傳送額外值),這裏傳送過去的btn1的id值。

 

然後通過PendingIntent.getBroadcast();將intent封裝到pendingIntent中,以待發送。
在構造了pendingIntent之後,就是將這個pendingIntent與btn1綁定,當用戶點擊btn1的時候,將廣播發送出去。代碼如下:

RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent);

最後,是將這個remoteView更新到所有widget上。(因爲用戶對某一個apps可以創建多個widget,要保持所有的widget狀態統一)

appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);

注意這裏有個appWidgetIds,這個參數是通過OnUpdate()傳過來的,它是一個int數組,裏面存儲了用戶所創建的所有widget的ID值。更新的時候也是通過widget的ID值,一個個更新的。
再絮叨一遍,我們這裏做了兩件事:

(1)、將按鈕控件(R.id.btn1)與pendingIntent綁定。當用戶點擊按鈕時,就會把所構造的intent發送出去。

涉及代碼爲:

Intent intent = new Intent();
intent.setClass(context, ExampleAppWidgetProvider.class);
intent.setData(Uri.parse("harvic:" + R.id.btn1));
 
// 設置pendingIntent的作用
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent, 0);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent);

(2)、更新所有的widget

主要利用updateAppWidget(appWidgetIds, remoteViews);將remoteView根據widget的id值,一個個更新界面,涉及代碼爲:

RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
// 更新Appwidget
appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);

可以看到,這裏如果不算綁定按鈕的話,其實什麼都沒有更新,因爲RemoteView除了綁定控件的點擊事件,還可以設置textView的文字,ImageVIew的圖片Resource等,我們這裏都還沒有涉及

2、接收廣播

由於我們在創建廣播的Intent時,使用的顯示Intent,所以我們的廣播不需要註冊就會發到這們這個類(ExampleAppWidgetProvider.java)裏面。

在接收到廣播後,我們先判斷Intent中是不是包含data,因爲我們在發送廣播時放data中塞了數據(btn1的ID),所以只要data中有數據就可以判定是用戶點擊btn1發來的廣播。然後同樣利用RemoteView將textView的文字改成btn click加一串隨機數字,代碼如下:

@Override
public void onReceive(Context context, Intent intent) {
	
	if (intent == null) {
		return;
	}
 
	String action = intent.getAction();
	if (broadCastString.equals(action)) {
		// 只能通過遠程對象來設置appwidget中的控件狀態
		RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
				R.layout.example_appwidget);
 
		// 通過遠程對象將按鈕的文字設置爲”一個隨機數”
		Random random1 = new Random();
		remoteViews.setTextViewText(R.id.text,"btn click:" + random1.nextInt());
 
		// 獲得appwidget管理實例,用於管理appwidget以便進行更新操作
		AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
 
		// 相當於獲得所有本程序創建的appwidget
		ComponentName componentName = new ComponentName(context,ExampleAppWidgetProvider.class);
 
		// 更新appwidget
		appWidgetManager.updateAppWidget(componentName, remoteViews);
	}
	super.onReceive(context, intent);
}

同樣還是利用updateAppWidget()函數來更新widget,對比OnUpdate()中的代碼,這裏有兩點不同:

1、少了btn綁定,僅僅是更新界面,代碼爲:

RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
		R.layout.example_appwidget);
 
// 通過遠程對象將按鈕的文字設置爲”一個隨機數”
Random random1 = new Random();
remoteViews.setTextViewText(R.id.text,"btn click:" + random1.nextInt());

在這裏,我們將R.id.text的文字更新爲btn click加一個隨機數。因爲每次點擊按鈕都會發送一個消息,所以每次點擊產生的隨機數是不同的,在界面上可以明顯的表現出來。

 

2、改變了更新界面的方式

在OnUpdate中,我們更新界面是通過傳過來的widget的id數組來更新所有widget的。而這裏是通過獲取ComponentName來更新的。其實這裏還有另一種實現方式,即我們可以把OnUpdate中傳過來的appWidgetIds保存起來,在這裏同樣使用OnUpdate中的appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);來更新。但我更推薦這裏利用ComponentName的這種方式來更新,因爲當我們的應用程序進程一被殺掉,當應用程序再起來的時候,使用ComponentName這種更新方式的代碼還是可以繼續響應的,而利用保存appWidgetIds的代碼是不會響應的。
 

三、進階——如何響應多個按鈕控件

上面的例子中,我們簡單實現了所謂的交互,但並沒有辦法識別出當前是哪個控件發出的,並根據不同的控件做出響應,先看看這部分效果:

下面對上面的代碼進行補充,首先在發送時,就應該加以區別當前是哪個控件發出的intent,代碼如下:

private PendingIntent getPendingIntent(Context context,int resID){
	Intent intent = new Intent();
	intent.setClass(context, ExampleAppWidgetProvider.class);
	intent.setData(Uri.parse("harvic:" + resID));
	
	PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent,0);
	return pendingIntent;
}
 
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
		int[] appWidgetIds) {
	RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
	
	remoteViews.setOnClickPendingIntent(R.id.btn1, getPendingIntent(context, R.id.btn1));
	
	remoteViews.setOnClickPendingIntent(R.id.btn2, getPendingIntent(context, R.id.btn2));
	// 更新Appwidget
	appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
}

先看OnUpdate中設置RemoteView的代碼:

RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
 
remoteViews.setOnClickPendingIntent(R.id.btn1, getPendingIntent(context, R.id.btn1));
 
remoteViews.setOnClickPendingIntent(R.id.btn2, getPendingIntent(context, R.id.btn2));
// 更新Appwidget
appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);

創建一個RemoteView,然後將btn1,btn2分別進行綁定,這裏我將PendingIntetn進行了封裝:

private PendingIntent getPendingIntent(Context context,int resID){
	Intent intent = new Intent();
	intent.setClass(context, ExampleAppWidgetProvider.class);
	intent.setData(Uri.parse("harvic:" + resID));
	
	PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent,0);
	return pendingIntent;
}

要設置data域的時候,把控件ID設置進去,因爲我們在綁定的時候,是將同一個ID綁定在一起的,所以哪個控件點擊,發送的intent中data中的id就是哪個控件的id,綁定Id的代碼就是下面這行:

remoteViews.setOnClickPendingIntent(R.id.btn2, getPendingIntent(context, R.id.btn2));

然後就是接收的部分:

 

接收時主要就是先根據傳送過來的Intent,找到data中的控件id:

Uri data = intent.getData();
int resID = -1;
if(data != null){
	resID = Integer.parseInt(data.getSchemeSpecificPart());
}

然後根據ID,定製RemoteView的TextView的字體內容

RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
Random random1 = new Random();
 
switch (resID) {
case R.id.btn1:		
	remoteViews.setTextViewText(R.id.text,"btn1 click:" + random1.nextInt());
	break;
case R.id.btn2:
	remoteViews.setTextViewText(R.id.text,"btn2 click:" + random1.nextInt());
	break;
}

同樣,最後是更新所有的wiget界面:

// 獲得appwidget管理實例,用於管理appwidget以便進行更新操作
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
// 相當於獲得所有本程序創建的appwidget
ComponentName componentName = new ComponentName(context,ExampleAppWidgetProvider.class);
// 更新appwidget
appWidgetManager.updateAppWidget(componentName, remoteViews);

經過上面的講解,總體的接收代碼就是這樣的:

@Override
public void onReceive(Context context, Intent intent) {
	
	Uri data = intent.getData();
	int resID = -1;
	if(data != null){
		resID = Integer.parseInt(data.getSchemeSpecificPart());
	}
	
	// 通過遠程對象將按鈕的文字設置爲”一個隨機數”
	RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
	Random random1 = new Random();
 
	switch (resID) {
	case R.id.btn1:		
		remoteViews.setTextViewText(R.id.text,"btn1 click:" + random1.nextInt());
		break;
	case R.id.btn2:
		remoteViews.setTextViewText(R.id.text,"btn2 click:" + random1.nextInt());
		break;
	}
 
	// 獲得appwidget管理實例,用於管理appwidget以便進行更新操作
	AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
	// 相當於獲得所有本程序創建的appwidget
	ComponentName componentName = new ComponentName(context,ExampleAppWidgetProvider.class);
	// 更新appwidget
	appWidgetManager.updateAppWidget(componentName, remoteViews);
 
	super.onReceive(context, intent);
}

好了,到這所有的代碼都講完了。

四、附(通過匿名Intent發送廣播------不推薦):

在很多例子中使用的是匿名Intent來發送廣播,即設定intent的Action來發送廣播,這種方法我是極不推薦的,因爲不能識別發送控件的id,這主要是由於pendingIntent的原因。針對這個工程,我也寫了一個匿名Intent廣播的例子,在這我就不講了,大家可以看源碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章