溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

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

發(fā)布時(shí)間:2020-05-30 09:00:24 來(lái)源:網(wǎng)絡(luò) 閱讀:538 作者:Android_JIE 欄目:移動(dòng)開(kāi)發(fā)

什么是AppWidget

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

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

圖中我用×××箭頭指示的即為AppWidget,一些用戶(hù)使用比較頻繁的程序,可以做成AppWidget,這樣能方便地使用。典型的程序有時(shí)鐘、天氣、音樂(lè)播放器等。AppWidget 是Android 系統(tǒng)應(yīng)用開(kāi)發(fā)層面的一部分,有著特殊用途,使用得當(dāng)?shù)幕?,的確會(huì)為app 增色不少,它的工作原理是把一個(gè)進(jìn)程的控件嵌入到別外一個(gè)進(jìn)程的窗口里的一種方法。長(zhǎng)按桌面空白處,會(huì)出現(xiàn)一個(gè) AppWidget 的文件夾,在里面找到相應(yīng)的 AppWidget ,長(zhǎng)按拖出,即可將 AppWidget 添加到桌面,

如何開(kāi)發(fā)AppWidget

AppWidget 是通過(guò) BroadCastReceiver 的形式進(jìn)行控制的,開(kāi)發(fā) AppWidget 的主要類(lèi)為 AppWidgetProvider, 該類(lèi)繼承自 BroadCastReceiver。為了實(shí)現(xiàn)桌面小部件,開(kāi)發(fā)者只要開(kāi)發(fā)一個(gè)繼承自 AppWidgetProvider 的子類(lèi),并重寫(xiě)它的 onUpdate() 方法即可。重寫(xiě)該方法,一般來(lái)說(shuō)可按如下幾個(gè)步驟進(jìn)行:

1、創(chuàng)建一個(gè) RemoteViews 對(duì)象,這個(gè)對(duì)象加載時(shí)指定了桌面小部件的界面布局文件。

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

3、創(chuàng)建一個(gè) ComponentName 對(duì)象

4、調(diào)用 AppWidgetManager 更新桌面小部件。

下面來(lái)看一個(gè)實(shí)際的例子,用 Android Studio 自動(dòng)生成的例子來(lái)說(shuō)。

新建了一個(gè) HelloWorld 項(xiàng)目,然后新建一個(gè) AppWidget ,命名為 MyAppWidgetProvider,按默認(rèn)下一步,就完成了一個(gè)最簡(jiǎn)單的AppWidget的開(kāi)發(fā)。運(yùn)行程序之后,將小部件添加到桌面。操作步驟和默認(rèn)效果如下:

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

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

我們看看 AS 為我們自動(dòng)生成了哪些代碼呢?對(duì)照著上面說(shuō)的的步驟我們來(lái)看看。

首先,有一個(gè) MyAppWidgetProvider 的類(lèi)。

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
 }
}

該類(lèi)繼承自 AppWidgetProvider ,AS默認(rèn)幫我們重寫(xiě) onUpdate() 方法,遍歷 appWidgetIds, 調(diào)用了 updateAppWidget() 方法。再看 updateAppWidget() 方法,很簡(jiǎn)單,只有四行:

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

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

創(chuàng)建了一個(gè) RemoteViews 對(duì)象,第一個(gè)參數(shù)傳應(yīng)用程序包名,第二個(gè)參數(shù)指定了,RemoteViews 加載的布局文件。這一行對(duì)應(yīng)上面步驟中說(shuō)的第一點(diǎn)。可以看到在 res/layout/ 目錄下面 AS 自動(dòng)生成了一個(gè) my_app_widget_provider.xml 文件,內(nèi)容如下:

`<``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``>`

這個(gè)文件就是我們最后看到的桌面小部件的樣子,布局文件中只有一個(gè)TextView。這是你可能會(huì)問(wèn),想要加圖片可以嗎?可以,就像正常的Activity布局一樣添加 ImageView 就行了,聰明的你可能開(kāi)始想自定義小部件的樣式了,添加功能強(qiáng)大外觀漂亮逼格高的自定義控件了,很遺憾,不可以。小部件布局文件可以添加的組件是有限制的,詳細(xì)內(nèi)容在下文介紹RemoteViews 時(shí)再說(shuō)。

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

將第一行聲明的字符串賦值給上面布局文件中的 TextView,注意這里賦值時(shí),指定TextView的 id,要對(duì)應(yīng)起來(lái)。這一行對(duì)于了上面步驟中的第二點(diǎn)。

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

這里調(diào)用了 appWidgetManager.updateAppWidget() 方法,更新小部件。這一行對(duì)應(yīng)了上面步驟中的第四點(diǎn)。

這時(shí),你可能有疑問(wèn)了,上面明明說(shuō)了四個(gè)步驟,其中第三步,創(chuàng)建一個(gè) ComponentName 對(duì)象,明明就不需要。的確,這個(gè)例子中也沒(méi)有用到。如果我們手敲第四步代碼,AS的智能提示會(huì)告訴你,appWidgetManager.updateAppWidget() 有三個(gè)重載的方法。源碼中三個(gè)方法沒(méi)有寫(xiě)在一起,為了方便,這里我復(fù)制貼出官方 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.

這個(gè)三個(gè)方法都接收兩個(gè)參數(shù),第二個(gè)參數(shù)都是 RemoteViews 對(duì)象。其中第一個(gè)方法的第一個(gè)參數(shù)就是 ComponentName 對(duì)象,更新所有的 AppWidgetProvider 提供的所有的 AppWidget 實(shí)例,第二個(gè)方法時(shí)更新明確指定 Id 的 AppWidget 的對(duì)象集,第三個(gè)方法,更新明確指定 Id 的某個(gè) AppWidget 對(duì)象。所以一般我們使用第一個(gè)方法,針對(duì)所有的 AppWidget 對(duì)象,我們也可以根據(jù)需要選擇性地去更新。

到這里,所有步驟都結(jié)束了,就完了?還沒(méi)。前面說(shuō)了,自定義的 MyAppWidgetProvider 繼承自 AppWidgetProvider,而 AppWidgetProvider 又是繼承自 BroadCastReceiver,

所以說(shuō) MyAppWidgetProvider 本質(zhì)上是一個(gè)廣播接受者,屬于四大組件之一,需要我們的清單文件中注冊(cè)。打開(kāi)AndroidManifest.xml文件可以看到,的確是注冊(cè)了小部件的,內(nèi)容如下:

<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>

上面代碼中有一個(gè) Action,這個(gè) Action 必須要加,且不能更改,屬于系統(tǒng)規(guī)范,是作為小部件的標(biāo)識(shí)而存在的。如果不加,這個(gè) Receiver 就不會(huì)出現(xiàn)在小部件列表里面。然后看到小部件指定了 @xml/my_app_widget_provider_info 作為meta-data,細(xì)心的你發(fā)現(xiàn)了,在 res/ 目錄下面建立了一個(gè) xml 文件夾,下面新建了一個(gè) my_app_widget_provider_info.xml 文件,內(nèi)容如下:

`<?``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 指定了小部件在小部件列表里的預(yù)覽圖,updatePeriodMillis 指定了小部件更新周期,單位為毫秒。更多屬性,可以查看API文檔。

到這里,上面這個(gè)極簡(jiǎn)單的小部件開(kāi)發(fā)過(guò)程就真的結(jié)束了。為了開(kāi)發(fā)出更強(qiáng)大一點(diǎn)小部件,我們還需要進(jìn)一步了解 RemoteViews 和 AppWidgetProvider。

AppWidget的妝容——RemoteViews

下面簡(jiǎn)單說(shuō)說(shuō) RemoteViews 相關(guān)的幾個(gè)類(lèi)。

1.1 RemoteViews

RemoteViews,從字面意思理解為它是一個(gè)遠(yuǎn)程視圖。是一種遠(yuǎn)程的 View,它在其它進(jìn)程中顯示,卻可以在另一個(gè)進(jìn)程中更新。RemoteViews 在Android中的使用場(chǎng)景主要有:自定義通知欄和桌面小部件。

在RemoteViews 的構(gòu)造函數(shù)中,第二個(gè)參數(shù)接收一個(gè) layout 文件來(lái)確定 RemoteViews 的視圖;然后,我們調(diào)用RemoteViews 中的 set 方法對(duì) layout 中的各個(gè)組件進(jìn)行設(shè)置,例如,可以調(diào)用 setTextViewText() 來(lái)設(shè)置 TextView 組件的文本。

前面提到,小部件布局文件可以添加的組件是有限制的,它可以支持的 View 類(lèi)型包括四種布局:FrameLayout、LinearLayout、RelativeLayout、GridLayout 和 13 種View: AnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView、ViewFlipper、ListView、GridView、StackView、AdapterViewFlipper、ViewSub。注意:RemoteViews 也并不支持上述 View 的子類(lèi)。

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

1.2 RemoteViewsService

RemoteViewsService,是管理RemoteViews的服務(wù)。一般,當(dāng)AppWidget 中包含 GridView、ListView、StackView 等集合視圖時(shí),才需要使用RemoteViewsService來(lái)進(jìn)行更新、管理。RemoteViewsService 更新集合視圖的一般步驟是:

(01) 通過(guò) setRemoteAdapter() 方法來(lái)設(shè)置 RemoteViews 對(duì)應(yīng) RemoteViewsService 。

(02) 之后在 RemoteViewsService 中,實(shí)現(xiàn) RemoteViewsFactory 接口。然后,在 RemoteViewsFactory 接口中對(duì)集合視圖的各個(gè)子項(xiàng)進(jìn)行設(shè)置,例如 ListView 中的每一Item。

1.3 RemoteViewsFactory

通過(guò)RemoteViewsService中的介紹,我們知道RemoteViewsService是通過(guò) RemoteViewsFactory來(lái)具體管理layout中集合視圖的,RemoteViewsFactory是RemoteViewsService中的一個(gè)內(nèi)部接口。RemoteViewsFactory提供了一系列的方法管理集合視圖中的每一項(xiàng)。例如:

RemoteViews getViewAt(int position)

通過(guò)getViewAt()來(lái)獲取“集合視圖”中的第position項(xiàng)的視圖,視圖是以RemoteViews的對(duì)象返回的。

int getCount()

通過(guò)getCount()來(lái)獲取“集合視圖”中所有子項(xiàng)的總數(shù)。

AppWidget的美貌——AppWidgetProvider

我們說(shuō)一位女同事漂亮,除了因?yàn)樗┑囊路?、化的妝漂亮以外,我想最主要的原因還是她本人長(zhǎng)的漂亮吧。同樣,小部件之所以有附著在桌面,跨進(jìn)程更新 View 的能力,主要是因?yàn)锳ppWidgetProvider 是一個(gè)廣播接收者。

我們發(fā)現(xiàn),上面的例子中,AS 幫我們自動(dòng)生成的代碼中,除了 onUpdate() 方法被我們重寫(xiě)了,還有重寫(xiě) onEnable() 和 onDisable() 兩個(gè)方法,但都是空實(shí)現(xiàn),這兩個(gè)方法什么時(shí)候會(huì)被調(diào)用?還有,我們說(shuō)自定義的 MyAppWidgetProvider,繼承自 AppWidgetProvider,而 MyAppWidgetProvider 又是BroadCastReceiver 的子類(lèi),而我們卻沒(méi)有向?qū)懗R?guī)廣播接收者一樣重寫(xiě) onReceiver() 方法?下面跟進(jìn)去 AppWidgetProvider 源碼,一探究竟。

這個(gè)類(lèi)代碼并不多,其實(shí),AppWidgetProvider 出去構(gòu)造方法外,總共只有下面這些方法:

onEnable() :當(dāng)小部件第一次被添加到桌面時(shí)回調(diào)該方法,可添加多次,但只在第一次調(diào)用。對(duì)用廣播的 Action 為 ACTION_APPWIDGET_ENABLE。

onUpdate(): 當(dāng)小部件被添加時(shí)或者每次小部件更新時(shí)都會(huì)調(diào)用一次該方法,配置文件中配置小部件的更新周期 updatePeriodMillis,每次更新都會(huì)調(diào)用。對(duì)應(yīng)廣播 Action 為:ACTION_APPWIDGET_UPDATE 和 ACTION_APPWIDGET_RESTORED 。

onDisabled(): 當(dāng)最后一個(gè)該類(lèi)型的小部件從桌面移除時(shí)調(diào)用,對(duì)應(yīng)的廣播的 Action 為 ACTION_APPWIDGET_DISABLED。

onDeleted(): 每刪除一個(gè)小部件就調(diào)用一次。對(duì)應(yīng)的廣播的 Action 為: ACTION_APPWIDGET_DELETED 。

onRestored(): 當(dāng)小部件從備份中還原,或者恢復(fù)設(shè)置的時(shí)候,會(huì)調(diào)用,實(shí)際用的比較少。對(duì)應(yīng)廣播的 Action 為 ACTION_APPWIDGET_RESTORED。

onAppWidgetOptionsChanged(): 當(dāng)小部件布局發(fā)生更改的時(shí)候調(diào)用。對(duì)應(yīng)廣播的 Action 為 ACTION_APPWIDGET_OPTIONS_CHANGED。

最后就是 onReceive() 方法了,AppWidgetProvider 重寫(xiě)了該方法,用于分發(fā)具體的時(shí)間給上述的方法??纯丛创a:

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 練習(xí)

下面再自己寫(xiě)個(gè)例子,學(xué)習(xí) RemoteViews 中的其它知識(shí)點(diǎn),這個(gè)例子中小部件布局中用到 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``=``"點(diǎn)擊跳轉(zhuǎn)"``/>`

`</``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, "點(diǎn)擊跳轉(zhuǎn)到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);

 // 設(shè)置 ListView 的adapter。
 // (01) intent: 對(duì)應(yīng)啟動(dòng) ListViewService(RemoteViewsService) 的intent
 // (02) setRemoteAdapter: 設(shè)置 ListView 的適配器
 // 通過(guò)setRemoteAdapter將 ListView 和ListViewService關(guān)聯(lián)起來(lái),
 // 以達(dá)到通過(guò) 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);

 // 設(shè)置響應(yīng) ListView 的intent模板
 // 說(shuō)明:“集合控件(如GridView、ListView、StackView等)”中包含很多子元素,如GridView包含很多格子。
 // 它們不能像普通的按鈕一樣通過(guò) setOnClickPendingIntent 設(shè)置點(diǎn)擊事件,必須先通過(guò)兩步。
 // (01) 通過(guò) setPendingIntentTemplate 設(shè)置 “intent模板”,這是比不可少的!
 // (02) 然后在處理該“集合控件”的RemoteViewsFactory類(lèi)的getViewAt()接口中 通過(guò) setOnClickFillInIntent 設(shè)置“集合控件的某一項(xiàng)的數(shù)據(jù)”

 /*
  * setPendingIntentTemplate 設(shè)置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);
 }
}

下面重點(diǎn)是 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 它默認(rèn)的就有action 是provider中使用 setPendingIntentTemplate 設(shè)置的action*/
  views.setOnClickFillInIntent(android.R.id.text1, changeIntent);
  return views;
 }

 /* 在更新界面的時(shí)候如果耗時(shí)就會(huì)顯示 正在加載... 的默認(rèn)字樣,但是你可以更改這個(gè)界面
  * 如果返回null 顯示默認(rèn)界面
  * 否則 加載自定義的,返回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``>`

這個(gè)小部件添加到桌面后有一個(gè) ImageView 顯示小機(jī)器人,下面有一個(gè) Button ,右邊有一個(gè)ListView。

這里主要看看,Button 和 ListView 在 RemoteViews中如何使用。、

Button 設(shè)置 Text 和 TextView 一樣,因?yàn)?Button 本身繼承自 TextView,Button 設(shè)置點(diǎn)擊事件如下:

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 , 與通知中的用法一樣。這里點(diǎn)擊之后跳轉(zhuǎn)到了 MainActivity。

關(guān)于 ListView 的用法就復(fù)雜一些了。首先需要自定義一個(gè)類(lèi)繼承自 RemoteViewsServices ,并重寫(xiě) onGetViewFactory 方法,返回 RemoteViewsService.RemoteViewsFactory 接口的對(duì)象。這里定義了一個(gè)內(nèi)部類(lèi)實(shí)現(xiàn)該接口,需要重寫(xiě)多個(gè)方法,與 ListView 的多布局適配很類(lèi)似。重點(diǎn)方法是

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

程序運(yùn)行效果如下圖:
Android桌面小部件AppWidget開(kāi)發(fā)

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI