這篇我們依然撇開播放器,講一講桌面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廣播的例子,在這我就不講了,大家可以看源碼。