Android桌面小部件AppWidget開發(fā)

什么是AppWidget

AppWidget 即桌面小部件唬渗,也叫桌面控件垂涯,就是能直接顯示在Android系統(tǒng)桌面上的小程序嵌巷,先看圖:

圖中我用黃色箭頭指示的即為AppWidget重窟,一些用戶使用比較頻繁的程序鸟雏,可以做成AppWidget享郊,這樣能方便地使用。典型的程序有時鐘孝鹊、天氣炊琉、音樂播放器等。AppWidget 是Android 系統(tǒng)應用開發(fā)層面的一部分又活,有著特殊用途苔咪,使用得當?shù)幕拇_會為app 增色不少柳骄,它的工作原理是把一個進程的控件嵌入到別外一個進程的窗口里的一種方法团赏。長按桌面空白處,會出現(xiàn)一個 AppWidget 的文件夾耐薯,在里面找到相應的 AppWidget 舔清,長按拖出,即可將 AppWidget 添加到桌面曲初,

如何開發(fā)AppWidget

AppWidget 是通過 BroadCastReceiver 的形式進行控制的体谒,開發(fā) AppWidget 的主要類為 AppWidgetProvider, 該類繼承自 BroadCastReceiver。為了實現(xiàn)桌面小部件臼婆,開發(fā)者只要開發(fā)一個繼承自 AppWidgetProvider 的子類抒痒,并重寫它的 onUpdate() 方法即可。重寫該方法颁褂,一般來說可按如下幾個步驟進行:

1故响、創(chuàng)建一個 RemoteViews 對象,這個對象加載時指定了桌面小部件的界面布局文件颁独。

2被去、設置 RemoteViews 創(chuàng)建時加載的布局文件中各個元素的屬性。

3奖唯、創(chuàng)建一個 ComponentName 對象

4惨缆、調用 AppWidgetManager 更新桌面小部件。

下面來看一個實際的例子丰捷,用 Android Studio 自動生成的例子來說坯墨。

新建了一個 HelloWorld 項目,然后新建一個 AppWidget 病往,命名為 MyAppWidgetProvider捣染,按默認下一步,就完成了一個最簡單的AppWidget的開發(fā)停巷。運行程序之后耍攘,將小部件添加到桌面榕栏。操作步驟和默認效果如下:

我們看看 AS 為我們自動生成了哪些代碼呢?對照著上面說的的步驟我們來看看蕾各。

首先扒磁,有一個 MyAppWidgetProvider 的類。

package com.example.joy.remoteviewstest;
 
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;
 
/**
 * Implementation of App Widget functionality.
 */
public class MyAppWidgetProvider extends AppWidgetProvider {
 
 static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
    int appWidgetId) {
 
 CharSequence widgetText = context.getString(R.string.appwidget_text);  
 // Construct the RemoteViews object
 RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_app_widget_provider);
 views.setTextViewText(R.id.appwidget_text, widgetText);
 
 // Instruct the widget manager to update the widget
 appWidgetManager.updateAppWidget(appWidgetId, views);
 }
 
 @Override
 public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
 // There may be multiple widgets active, so update all of them
 for (int appWidgetId : appWidgetIds) {
  updateAppWidget(context, appWidgetManager, appWidgetId);
 }
 }
 
 @Override
 public void onEnabled(Context context) {
 // Enter relevant functionality for when the first widget is created
 }
 
 @Override
 public void onDisabled(Context context) {
 // Enter relevant functionality for when the last widget is disabled
 }
}

該類繼承自 AppWidgetProvider 式曲,AS默認幫我們重寫 onUpdate() 方法妨托,遍歷 appWidgetIds, 調用了 updateAppWidget() 方法。再看 updateAppWidget() 方法吝羞,很簡單兰伤,只有四行:

第一行,CharSequence widgetText = context.getString(R.string.appwidget_text);聲明了一個字符串钧排;

第二行敦腔,RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_app_widget_provider);

創(chuàng)建了一個 RemoteViews 對象,第一個參數(shù)傳應用程序包名,第二個參數(shù)指定了恨溜,RemoteViews 加載的布局文件会烙。這一行對應上面步驟中說的第一點⊥厕啵可以看到在 res/layout/ 目錄下面 AS 自動生成了一個 my_app_widget_provider.xml 文件,內容如下:

`<``RelativeLayout` `xmlns:android``=``"[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)"`

`android:layout_width``=``"match_parent"`

`android:layout_height``=``"match_parent"`

`android:background``=``"#09C"`

`android:padding``=``"@dimen/widget_margin"``>`

`<``TextView`

`android:id``=``"@+id/appwidget_text"`

`android:layout_width``=``"wrap_content"`

`android:layout_height``=``"wrap_content"`

`android:layout_centerHorizontal``=``"true"`

`android:layout_centerVertical``=``"true"`

`android:layout_margin``=``"8dp"`

`android:background``=``"#09C"`

`android:contentDescription``=``"@string/appwidget_text"`

`android:text``=``"@string/appwidget_text"`

`android:textColor``=``"#ffffff"`

`android:textStyle``=``"bold|italic"` `/>`

`</``RelativeLayout``>`

這個文件就是我們最后看到的桌面小部件的樣子纸厉,布局文件中只有一個TextView系吭。這是你可能會問,想要加圖片可以嗎颗品?可以肯尺,就像正常的Activity布局一樣添加 ImageView 就行了,聰明的你可能開始想自定義小部件的樣式了躯枢,添加功能強大外觀漂亮逼格高的自定義控件了则吟,很遺憾,不可以锄蹂。小部件布局文件可以添加的組件是有限制的氓仲,詳細內容在下文介紹RemoteViews 時再說。

第三行得糜,views.setTextViewText(R.id.appwidget_text, widgetText);

將第一行聲明的字符串賦值給上面布局文件中的 TextView,注意這里賦值時敬扛,指定TextView的 id,要對應起來朝抖。這一行對于了上面步驟中的第二點啥箭。

第四行,appWidgetManager.updateAppWidget(appWidgetId, views);

這里調用了 appWidgetManager.updateAppWidget() 方法治宣,更新小部件急侥。這一行對應了上面步驟中的第四點砌滞。

這時,你可能有疑問了坏怪,上面明明說了四個步驟贝润,其中第三步,創(chuàng)建一個 ComponentName 對象陕悬,明明就不需要题暖。的確,這個例子中也沒有用到捉超。如果我們手敲第四步代碼胧卤,AS的智能提示會告訴你,appWidgetManager.updateAppWidget() 有三個重載的方法拼岳。源碼中三個方法沒有寫在一起枝誊,為了方便,這里我復制貼出官方 API 中的介紹

void    
  updateAppWidget(ComponentName provider, RemoteViews views)

Set the RemoteViews to use for all AppWidget instances for the supplied AppWidget provider.
 void   
 updateAppWidget(int[] appWidgetIds, RemoteViews views)

Set the RemoteViews to use for the specified appWidgetIds.
void    
updateAppWidget(int appWidgetId, RemoteViews views)

Set the RemoteViews to use for the specified appWidgetId.

這個三個方法都接收兩個參數(shù)惜纸,第二個參數(shù)都是 RemoteViews 對象叶撒。其中第一個方法的第一個參數(shù)就是 ComponentName 對象,更新所有的 AppWidgetProvider 提供的所有的 AppWidget 實例耐版,第二個方法時更新明確指定 Id 的 AppWidget 的對象集祠够,第三個方法,更新明確指定 Id 的某個 AppWidget 對象粪牲。所以一般我們使用第一個方法古瓤,針對所有的 AppWidget 對象腺阳,我們也可以根據需要選擇性地去更新。

到這里亭引,所有步驟都結束了,就完了焙蚓?還沒纹冤。前面說了,自定義的 MyAppWidgetProvider 繼承自 AppWidgetProvider购公,而 AppWidgetProvider 又是繼承自 BroadCastReceiver赵哲,

所以說 MyAppWidgetProvider 本質上是一個廣播接受者,屬于四大組件之一君丁,需要我們的清單文件中注冊枫夺。打開AndroidManifest.xml文件可以看到,的確是注冊了小部件的绘闷,內容如下:

<receiver android:name=".MyAppWidgetProvider">
   <intent-filter>
    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
 
<meta-data
 android:name="android.appwidget.provider"
 android:resource="@xml/my_app_widget_provider_info" />
</receiver>

上面代碼中有一個 Action橡庞,這個 Action 必須要加较坛,且不能更改,屬于系統(tǒng)規(guī)范扒最,是作為小部件的標識而存在的丑勤。如果不加,這個 Receiver 就不會出現(xiàn)在小部件列表里面吧趣。然后看到小部件指定了 @xml/my_app_widget_provider_info 作為meta-data法竞,細心的你發(fā)現(xiàn)了,在 res/ 目錄下面建立了一個 xml 文件夾强挫,下面新建了一個 my_app_widget_provider_info.xml 文件岔霸,內容如下:

`<?``xml` `version``=``"1.0"` `encoding``=``"utf-8"``?>`

`<``appwidget-provider` `xmlns:android``=``"[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)"`

`android:initialKeyguardLayout``=``"@layout/my_app_widget_provider"`

`android:initialLayout``=``"@layout/my_app_widget_provider"`

`android:minHeight``=``"40dp"`

`android:minWidth``=``"40dp"`

`android:previewImage``=``"@drawable/example_appwidget_preview"`

`android:resizeMode``=``"horizontal|vertical"`

`android:updatePeriodMillis``=``"86400000"`

`android:widgetCategory``=``"home_screen"``>`

`</``appwidget-provider``>`

這里配置了一些小部件的基本信息,常用的屬性有 initialLayout 就是小部件的初始化布局俯渤, minHeight 定義了小部件的最小高度呆细,previewImage 指定了小部件在小部件列表里的預覽圖,updatePeriodMillis 指定了小部件更新周期八匠,單位為毫秒絮爷。更多屬性,可以查看API文檔梨树。

到這里坑夯,上面這個極簡單的小部件開發(fā)過程就真的結束了。為了開發(fā)出更強大一點小部件柜蜈,我們還需要進一步了解 RemoteViews 和 AppWidgetProvider床嫌。

AppWidget的妝容——RemoteViews

下面簡單說說 RemoteViews 相關的幾個類厌处。

1.1 RemoteViews

RemoteViews岁疼,從字面意思理解為它是一個遠程視圖。是一種遠程的 View捷绒,它在其它進程中顯示,卻可以在另一個進程中更新椭住。RemoteViews 在Android中的使用場景主要有:自定義通知欄和桌面小部件字逗。

在RemoteViews 的構造函數(shù)中宅广,第二個參數(shù)接收一個 layout 文件來確定 RemoteViews 的視圖些举;然后,我們調用RemoteViews 中的 set 方法對 layout 中的各個組件進行設置驶臊,例如叼丑,可以調用 setTextViewText() 來設置 TextView 組件的文本。

前面提到幢码,小部件布局文件可以添加的組件是有限制的,它可以支持的 View 類型包括四種布局:FrameLayout店雅、LinearLayout贞铣、RelativeLayout、GridLayout 和 13 種View: AnalogClock辕坝、Button、Chronometer琳袄、ImageButton纺酸、ImageView窖逗、ProgressBar餐蔬、TextView、ViewFlipper仗考、ListView、GridView词爬、StackView、AdapterViewFlipper痪寻、ViewSub。注意:RemoteViews 也并不支持上述 View 的子類蛇尚。

RemoteViews 提供了一系列 setXXX() 方法來為小部件的子視圖設置屬性顾画。具體可以參考 API 文檔。

1.2 RemoteViewsService

RemoteViewsService研侣,是管理RemoteViews的服務。一般庶诡,當AppWidget 中包含 GridView、ListView末誓、StackView 等集合視圖時,才需要使用RemoteViewsService來進行更新迅栅、管理。RemoteViewsService 更新集合視圖的一般步驟是:

(01) 通過 setRemoteAdapter() 方法來設置 RemoteViews 對應 RemoteViewsService 读存。

(02) 之后在 RemoteViewsService 中,實現(xiàn) RemoteViewsFactory 接口让簿。然后秀睛,在 RemoteViewsFactory 接口中對集合視圖的各個子項進行設置,例如 ListView 中的每一Item居凶。

1.3 RemoteViewsFactory

通過RemoteViewsService中的介紹虫给,我們知道RemoteViewsService是通過 RemoteViewsFactory來具體管理layout中集合視圖的,RemoteViewsFactory是RemoteViewsService中的一個內部接口抹估。RemoteViewsFactory提供了一系列的方法管理集合視圖中的每一項。例如:

RemoteViews getViewAt(int position)

通過getViewAt()來獲取“集合視圖”中的第position項的視圖瓷式,視圖是以RemoteViews的對象返回的。

int getCount()

通過getCount()來獲取“集合視圖”中所有子項的總數(shù)贸典。

AppWidget的美貌——AppWidgetProvider

我們說一位女同事漂亮,除了因為她穿的衣服据过、化的妝漂亮以外,我想最主要的原因還是她本人長的漂亮吧妒挎。同樣绳锅,小部件之所以有附著在桌面鳞芙,跨進程更新 View 的能力期虾,主要是因為AppWidgetProvider 是一個廣播接收者原朝。

我們發(fā)現(xiàn),上面的例子中彻消,AS 幫我們自動生成的代碼中,除了 onUpdate() 方法被我們重寫了宾尚,還有重寫 onEnable() 和 onDisable() 兩個方法,但都是空實現(xiàn)御板,這兩個方法什么時候會被調用怠肋?還有淹朋,我們說自定義的 MyAppWidgetProvider础芍,繼承自 AppWidgetProvider,而 MyAppWidgetProvider 又是BroadCastReceiver 的子類惶楼,而我們卻沒有向寫常規(guī)廣播接收者一樣重寫 onReceiver() 方法?下面跟進去 AppWidgetProvider 源碼何陆,一探究竟豹储。

這個類代碼并不多,其實晃洒,AppWidgetProvider 出去構造方法外球及,總共只有下面這些方法:

onEnable() :當小部件第一次被添加到桌面時回調該方法呻疹,可添加多次,但只在第一次調用镊尺。對用廣播的 Action 為 ACTION_APPWIDGET_ENABLE庐氮。

onUpdate(): 當小部件被添加時或者每次小部件更新時都會調用一次該方法宋彼,配置文件中配置小部件的更新周期 updatePeriodMillis,每次更新都會調用音婶。對應廣播 Action 為:ACTION_APPWIDGET_UPDATE 和 ACTION_APPWIDGET_RESTORED 衣式。

onDisabled(): 當最后一個該類型的小部件從桌面移除時調用檐什,對應的廣播的 Action 為 ACTION_APPWIDGET_DISABLED。

onDeleted(): 每刪除一個小部件就調用一次住册。對應的廣播的 Action 為: ACTION_APPWIDGET_DELETED 界弧。

onRestored(): 當小部件從備份中還原搭综,或者恢復設置的時候,會調用条获,實際用的比較少帅掘。對應廣播的 Action 為 ACTION_APPWIDGET_RESTORED堂油。

onAppWidgetOptionsChanged(): 當小部件布局發(fā)生更改的時候調用。對應廣播的 Action 為 ACTION_APPWIDGET_OPTIONS_CHANGED吱窝。

最后就是 onReceive() 方法了院峡,AppWidgetProvider 重寫了該方法系宜,用于分發(fā)具體的時間給上述的方法盹牧。看看源碼:

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);
 } else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {
  Bundle extras = intent.getExtras();
  if (extras != null) {
  int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
  int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
  if (oldIds != null && oldIds.length > 0) {
   this.onRestored(context, oldIds, newIds);
   this.onUpdate(context, AppWidgetManager.getInstance(context), newIds);
  }
  }
 }
 }

AppWidget 練習

下面再自己寫個例子,學習 RemoteViews 中的其它知識點踩寇,這個例子中小部件布局中用到 button 和 listview俺孙。上代碼:

小部件的布局文件 mul_app_widget_provider.xml 如下:

`<?``xml` `version``=``"1.0"` `encoding``=``"utf-8"``?>`

`<``LinearLayout` `xmlns:android``=``"[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)"`

`android:orientation``=``"horizontal"`

`android:layout_width``=``"match_parent"`

`android:layout_height``=``"match_parent"``>`

`<``LinearLayout`

`android:layout_width``=``"100dp"`

`android:layout_height``=``"200dp"`

`android:orientation``=``"vertical"``>`

`<``ImageView`

`android:id``=``"@+id/iv_test"`

`android:layout_width``=``"match_parent"`

`android:layout_height``=``"100dp"`

`android:src``=``"@mipmap/ic_launcher"``/>`

`<``Button`

`android:id``=``"@+id/btn_test"`

`android:layout_width``=``"match_parent"`

`android:layout_height``=``"wrap_content"`

`android:text``=``"點擊跳轉"``/>`

`</``LinearLayout``>`

`<``TextView`

`android:layout_width``=``"1dp"`

`android:layout_height``=``"200dp"`

`android:layout_marginLeft``=``"5dp"`

`android:layout_marginRight``=``"5dp"`

`android:background``=``"#f00"``/>`

`<``ListView`

`android:id``=``"@+id/lv_test"`

`android:layout_width``=``"100dp"`

`android:layout_height``=``"200dp"``>`

`</``ListView``>`

`</``LinearLayout``>`

小部件的配置信息 mul_app_widget_provider_info.xml 如下:

`<?``xml` `version``=``"1.0"` `encoding``=``"utf-8"``?>`

`<``appwidget-provider` `xmlns:android``=``"[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)"`

`android:initialLayout``=``"@layout/mul_app_widget_provider"`

`android:minHeight``=``"200dp"`

`android:minWidth``=``"200dp"`

`android:previewImage``=``"@mipmap/a1"`

`android:updatePeriodMillis``=``"86400000"``>`

`</``appwidget-provider``>`

MulAppWidgetProvider.java:

package com.example.joy.remoteviewstest;
 
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.RemoteViews;
import android.widget.Toast;
 
public class MulAppWidgetProvider extends AppWidgetProvider {
 
 public static final String CHANGE_IMAGE = "com.example.joy.action.CHANGE_IMAGE";
 
 private RemoteViews mRemoteViews;
 private ComponentName mComponentName;
 
 private int[] imgs = new int[]{
  R.mipmap.a1,
  R.mipmap.b2,
  R.mipmap.c3,
  R.mipmap.d4,
  R.mipmap.e5,
  R.mipmap.f6
 };
 
 
 @Override
 public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
 mRemoteViews = new RemoteViews(context.getPackageName(), R.layout.mul_app_widget_provider);
 mRemoteViews.setImageViewResource(R.id.iv_test, R.mipmap.ic_launcher);
 mRemoteViews.setTextViewText(R.id.btn_test, "點擊跳轉到Activity");
 Intent skipIntent = new Intent(context, MainActivity.class);
 PendingIntent pi = PendingIntent.getActivity(context, 200, skipIntent, PendingIntent.FLAG_CANCEL_CURRENT);
 mRemoteViews.setOnClickPendingIntent(R.id.btn_test, pi);
 
 // 設置 ListView 的adapter荣茫。
 // (01) intent: 對應啟動 ListViewService(RemoteViewsService) 的intent
 // (02) setRemoteAdapter: 設置 ListView 的適配器
 // 通過setRemoteAdapter將 ListView 和ListViewService關聯(lián)起來啡莉,
 // 以達到通過 GridWidgetService 更新 gridview 的目的
 Intent lvIntent = new Intent(context, ListViewService.class);
 mRemoteViews.setRemoteAdapter(R.id.lv_test, lvIntent);
 mRemoteViews.setEmptyView(R.id.lv_test,android.R.id.empty);
 
 // 設置響應 ListView 的intent模板
 // 說明:“集合控件(如GridView港准、ListView浅缸、StackView等)”中包含很多子元素衩椒,如GridView包含很多格子哮兰。
 // 它們不能像普通的按鈕一樣通過 setOnClickPendingIntent 設置點擊事件喝滞,必須先通過兩步。
 // (01) 通過 setPendingIntentTemplate 設置 “intent模板”冀痕,這是比不可少的狸演!
 // (02) 然后在處理該“集合控件”的RemoteViewsFactory類的getViewAt()接口中 通過 setOnClickFillInIntent 設置“集合控件的某一項的數(shù)據”
  
 /*
  * setPendingIntentTemplate 設置pendingIntent 模板
  * setOnClickFillInIntent 可以將fillInIntent 添加到pendingIntent中
  */
 Intent toIntent = new Intent(CHANGE_IMAGE);
 PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 200, toIntent, PendingIntent.FLAG_UPDATE_CURRENT);
 mRemoteViews.setPendingIntentTemplate(R.id.lv_test, pendingIntent);
 
 
 mComponentName = new ComponentName(context, MulAppWidgetProvider.class);
 appWidgetManager.updateAppWidget(mComponentName, mRemoteViews);
 }
 
 @Override
 public void onReceive(Context context, Intent intent) {
 super.onReceive(context, intent);
 if(TextUtils.equals(CHANGE_IMAGE,intent.getAction())){
  Bundle extras = intent.getExtras();
  int position = extras.getInt(ListViewService.INITENT_DATA);
  mRemoteViews = new RemoteViews(context.getPackageName(), R.layout.mul_app_widget_provider);
  mRemoteViews.setImageViewResource(R.id.iv_test, imgs[position]);
  mComponentName = new ComponentName(context, MulAppWidgetProvider.class);
  AppWidgetManager.getInstance(context).updateAppWidget(mComponentName, mRemoteViews);
 }
 }
}

MainActivity.java:

ge com.example.joy.remoteviewstest;
 
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
 
public class MainActivity extends AppCompatActivity {
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 }
}

下面重點是 ListView 在小部件中的用法:

 com.example.joy.remoteviewstest;
 
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
 
import java.util.ArrayList;
import java.util.List;
 
public class ListViewService extends RemoteViewsService {
 public static final String INITENT_DATA = "extra_data";
 
 @Override
 public RemoteViewsFactory onGetViewFactory(Intent intent) {
 return new ListRemoteViewsFactory(this.getApplicationContext(), intent);
 }
 
 private class ListRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
 
 private Context mContext;
 
 private List<String> mList = new ArrayList<>();
 
 public ListRemoteViewsFactory(Context context, Intent intent) {
  mContext = context;
 }
 
 @Override
 public void onCreate() {
  mList.add("一");
  mList.add("二");
  mList.add("三");
  mList.add("四");
  mList.add("五");
  mList.add("六");
 }
 
 @Override
 public void onDataSetChanged() {
 
 }
 
 @Override
 public void onDestroy() {
  mList.clear();
 }
 
 @Override
 public int getCount() {
  return mList.size();
 }
 
 @Override
 public RemoteViews getViewAt(int position) {
  RemoteViews views = new RemoteViews(mContext.getPackageName(), android.R.layout.simple_list_item_1);
  views.setTextViewText(android.R.id.text1, "item:" + mList.get(position));
 
  Bundle extras = new Bundle();
  extras.putInt(ListViewService.INITENT_DATA, position);
  Intent changeIntent = new Intent();
  changeIntent.setAction(MulAppWidgetProvider.CHANGE_IMAGE);
  changeIntent.putExtras(extras);
 
  /* android.R.layout.simple_list_item_1 --- id --- text1
  * listview的item click:將 changeIntent 發(fā)送腊尚,
  * changeIntent 它默認的就有action 是provider中使用 setPendingIntentTemplate 設置的action*/
  views.setOnClickFillInIntent(android.R.id.text1, changeIntent);
  return views;
 }
 
 /* 在更新界面的時候如果耗時就會顯示 正在加載... 的默認字樣婿斥,但是你可以更改這個界面
  * 如果返回null 顯示默認界面
  * 否則 加載自定義的民宿,返回RemoteViews
  */
 @Override
 public RemoteViews getLoadingView() {
  return null;
 }
 
 @Override
 public int getViewTypeCount() {
  return 1;
 }
 
 @Override
 public long getItemId(int position) {
  return position;
 }
 
 @Override
 public boolean hasStableIds() {
  return false;
 }
 }
}

最后看看清單文件:

`<?``xml` `version``=``"1.0"` `encoding``=``"utf-8"``?>`

`<``manifest` `xmlns:android``=``"[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)"`

`package``=``"com.example.joy.remoteviewstest"``>`

`<``application`

`android:allowBackup``=``"true"`

`android:icon``=``"@mipmap/ic_launcher"`

`android:label``=``"@string/app_name"`

`android:supportsRtl``=``"true"`

`android:theme``=``"@style/AppTheme"``>`

`<``activity` `android:name``=``".MainActivity"``>`

`<``intent-filter``>`

`<``action` `android:name``=``"android.intent.action.MAIN"` `/>`

`<``category` `android:name``=``"android.intent.category.LAUNCHER"` `/>`

`</``intent-filter``>`

`</``activity``>`

`<``receiver` `android:name``=``".MulAppWidgetProvider"`

`android:label``=``"@string/app_name"``>`

`<``intent-filter``>`

`<``action` `android:name``=``"com.example.joy.action.CHANGE_IMAGE"``/>`

`<``action` `android:name``=``"android.appwidget.action.APPWIDGET_UPDATE"``/>`

`</``intent-filter``>`

`<``meta-data`

`android:name``=``"android.appwidget.provider"`

`android:resource``=``"@xml/mul_app_widget_provider_info"``>`

`</``meta-data``>`

`</``receiver``>`

`<``service` `android:name``=``".ListViewService"`

`android:permission``=``"android.permission.BIND_REMOTEVIEWS"`

`android:exported``=``"false"`

`android:enabled``=``"true"``/>`

`</``application``>`

`</``manifest``>`

這個小部件添加到桌面后有一個 ImageView 顯示小機器人活鹰,下面有一個 Button ,右邊有一個ListView志群。

這里主要看看锌云,Button 和 ListView 在 RemoteViews中如何使用桑涎。彬向、

Button 設置 Text 和 TextView 一樣娃胆,因為 Button 本身繼承自 TextView讲衫,Button 設置點擊事件如下:

Intent skipIntent = new Intent(context, MainActivity.class);
 PendingIntent pi = PendingIntent.getActivity(context, 200, skipIntent, PendingIntent.FLAG_CANCEL_CURRENT);
 mRemoteViews.setOnClickPendingIntent(R.id.btn_test, pi);

用到方法 setOnClickPendingIntent涉兽,PendingIntent 表示延遲的 Intent 枷畏, 與通知中的用法一樣。這里點擊之后跳轉到了 MainActivity虱饿。

關于 ListView 的用法就復雜一些了拥诡。首先需要自定義一個類繼承自 RemoteViewsServices ,并重寫 onGetViewFactory 方法氮发,返回 RemoteViewsService.RemoteViewsFactory 接口的對象渴肉。這里定義了一個內部類實現(xiàn)該接口,需要重寫多個方法爽冕,與 ListView 的多布局適配很類似仇祭。重點方法是

1
public RemoteViews getViewAt(int position){}
這個方法中指定了 ListView 的每一個 item 的布局以及內容,同時通過 setOnClickFillInIntent() 或者 setOnClickPendingIntent() 給 item 設置點擊事件颈畸。這里我實現(xiàn)的點擊 item,替換左邊的 ImageView 的圖片乌奇。重寫了 MulAppWidgetProvider 類的 onReceiver 方法礁苗,處理替換圖片的邏輯。

程序運行效果如下圖:


QQ圖片20190425161422.gif
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末迁霎,一起剝皮案震驚了整個濱河市考廉,隨后出現(xiàn)的幾起案子昌粤,更是在濱河造成了極大的恐慌凄贩,老刑警劉巖疲扎,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件椒丧,死亡現(xiàn)場離奇詭異,居然都是意外死亡浦译,警方通過查閱死者的電腦和手機帽哑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門祝拯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來佳头,“玉大人,你說我怎么就攤上這事亭珍∫蘩妫” “怎么了众羡?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵羊壹,是天一觀的道長油猫。 經常有香客問我情妖,道長毡证,這世上最難降的妖魔是什么情竹? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮涎嚼,結果婚禮上法梯,老公的妹妹穿的比我還像新娘。我一直安慰自己铛绰,他們只是感情好捂掰,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著塞俱,像睡著了一般卧土。 火紅的嫁衣襯著肌膚如雪尤莺。 梳的紋絲不亂的頭發(fā)上颤霎,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天晴音,我揣著相機與錄音锤躁,去河邊找鬼系羞。 笑死椒振,一個胖子當著我的面吹牛澎迎,可吹牛的內容都是我干的。 我是一名探鬼主播哮洽,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼剔桨!你這毒婦竟也來了洒缀?” 一聲冷哼從身側響起萨脑,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鹊杖,沒想到半個月后扛芽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體登下,經...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡叮喳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年嘲更,在試婚紗的時候發(fā)現(xiàn)自己被綠了揩瞪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宠哄。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖妇菱,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情闯团,我是刑警寧澤房交,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站刃唤,受9級特大地震影響,放射性物質發(fā)生泄漏硬霍。R本人自食惡果不足惜须尚,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一耐床、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧堪嫂,春花似錦木柬、人聲如沸皆串。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谤牡。三九已至,卻和暖如春姥宝,著一層夾襖步出監(jiān)牢的瞬間翅萤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工套么, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人壁公。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像囊陡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子撮竿,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內容