Android桌面小部件AppWidget開發

什麼是AppWidgetjava

AppWidget 即桌面小部件,也叫桌面控件,就是能直接顯示在Android系統桌面上的小程序,先看圖:android

image

圖中我用黃色箭頭指示的即爲AppWidget,一些用戶使用比較頻繁的程序,能夠作成AppWidget,這樣能方便地使用。典型的程序有時鐘、天氣、音樂播放器等。AppWidget 是Android 系統應用開發層面的一部分,有着特殊用途,使用得當的化,的確會爲app 增色很多,它的工做原理是把一個進程的控件嵌入到別外一個進程的窗口裏的一種方法。長按桌面空白處,會出現一個 AppWidget 的文件夾,在裏面找到相應的 AppWidget ,長按拖出,便可將 AppWidget 添加到桌面,小程序

如何開發AppWidgetbash

AppWidget 是經過 BroadCastReceiver 的形式進行控制的,開發 AppWidget 的主要類爲 AppWidgetProvider, 該類繼承自 BroadCastReceiver。爲了實現桌面小部件,開發者只要開發一個繼承自 AppWidgetProvider 的子類,並重寫它的 onUpdate() 方法便可。重寫該方法,通常來講可按以下幾個步驟進行:app

一、建立一個 RemoteViews 對象,這個對象加載時指定了桌面小部件的界面佈局文件。less

二、設置 RemoteViews 建立時加載的佈局文件中各個元素的屬性。ide

三、建立一個 ComponentName 對象函數

四、調用 AppWidgetManager 更新桌面小部件。佈局

下面來看一個實際的例子,用 Android Studio 自動生成的例子來講。學習

新建了一個 HelloWorld 項目,而後新建一個 AppWidget ,命名爲 MyAppWidgetProvider,按默認下一步,就完成了一個最簡單的AppWidget的開發。運行程序以後,將小部件添加到桌面。操做步驟和默認效果以下:

image

image

咱們看看 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);

建立了一個 RemoteViews 對象,第一個參數傳應用程序包名,第二個參數指定了,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() 方法,更新小部件。這一行對應了上面步驟中的第四點。

這時,你可能有疑問了,上面明明說了四個步驟,其中第三步,建立一個 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.
複製代碼

這個三個方法都接收兩個參數,第二個參數都是 RemoteViews 對象。其中第一個方法的第一個參數就是 ComponentName 對象,更新全部的 AppWidgetProvider 提供的全部的 AppWidget 實例,第二個方法時更新明確指定 Id 的 AppWidget 的對象集,第三個方法,更新明確指定 Id 的某個 AppWidget 對象。因此通常咱們使用第一個方法,針對全部的 AppWidget 對象,咱們也能夠根據須要選擇性地去更新。

到這裏,全部步驟都結束了,就完了?還沒。前面說了,自定義的 MyAppWidgetProvider 繼承自 AppWidgetProvider,而 AppWidgetProvider 又是繼承自 BroadCastReceiver,

因此說 MyAppWidgetProvider 本質上是一個廣播接受者,屬於四大組件之一,須要咱們的清單文件中註冊。打開AndroidManifest.xml文件能夠看到,的確是註冊了小部件的,內容以下:

<receiver android:name=".MyAppWidgetProvider">
&emsp;&emsp; <intent-filter>
&emsp;&emsp;&emsp;&emsp;<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 必需要加,且不能更改,屬於系統規範,是做爲小部件的標識而存在的。若是不加,這個 Receiver 就不會出如今小部件列表裏面。而後看到小部件指定了 @xml/my_app_widget_provider_info 做爲meta-data,細心的你發現了,在 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文檔。

到這裏,上面這個極簡單的小部件開發過程就真的結束了。爲了開發出更強大一點小部件,咱們還須要進一步瞭解 RemoteViews 和 AppWidgetProvider。

AppWidget的妝容——RemoteViews

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

1.1 RemoteViews

RemoteViews,從字面意思理解爲它是一個遠程視圖。是一種遠程的 View,它在其它進程中顯示,卻能夠在另外一個進程中更新。RemoteViews 在Android中的使用場景主要有:自定義通知欄和桌面小部件。

在RemoteViews 的構造函數中,第二個參數接收一個 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 中,實現 RemoteViewsFactory 接口。而後,在 RemoteViewsFactory 接口中對集合視圖的各個子項進行設置,例如 ListView 中的每一Item。

1.3 RemoteViewsFactory

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

RemoteViews getViewAt(int position)

經過getViewAt()來獲取「集合視圖」中的第position項的視圖,視圖是以RemoteViews的對象返回的。

int getCount()

經過getCount()來獲取「集合視圖」中全部子項的總數。

AppWidget的美貌——AppWidgetProvider

咱們說一位女同事漂亮,除了由於她穿的衣服、化的妝漂亮之外,我想最主要的緣由仍是她本人長的漂亮吧。一樣,小部件之因此有附着在桌面,跨進程更新 View 的能力,主要是由於AppWidgetProvider 是一個廣播接收者。

咱們發現,上面的例子中,AS 幫咱們自動生成的代碼中,除了 onUpdate() 方法被咱們重寫了,還有重寫 onEnable() 和 onDisable() 兩個方法,但都是空實現,這兩個方法何時會被調用?還有,咱們說自定義的 MyAppWidgetProvider,繼承自 AppWidgetProvider,而 MyAppWidgetProvider 又是BroadCastReceiver 的子類,而咱們卻沒有向寫常規廣播接收者同樣重寫 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(): 當小部件佈局發生更改的時候調用。對應廣播的 Action 爲 ACTION_APPWIDGET_OPTIONS_CHANGED。

最後就是 onReceive() 方法了,AppWidgetProvider 重寫了該方法,用於分發具體的時間給上述的方法。看看源碼:

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關聯起來,
 // 以達到經過 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 設置「集合控件的某一項的數據」
  
 /*
  * 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 發送,
  * 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 接口的對象。這裏定義了一個內部類實現該接口,須要重寫多個方法,與 ListView 的多佈局適配很相似。重點方法是

1 public RemoteViews getViewAt(int position){} 這個方法中指定了 ListView 的每個 item 的佈局以及內容,同時經過 setOnClickFillInIntent() 或者 setOnClickPendingIntent() 給 item 設置點擊事件。這裏我實現的點擊 item,替換左邊的 ImageView 的圖片。重寫了 MulAppWidgetProvider 類的 onReceiver 方法,處理替換圖片的邏輯。

程序運行效果以下圖:

QQ圖片20190425161422.gif
相關文章
相關標籤/搜索