Android之AppWidget(桌面小部件)開發(fā)淺析
什么是AppWidget
AppWidget 即桌面小部件,也叫桌面控件,就是能直接顯示在Android系統(tǒng)桌面上的小程序,先看圖:

圖中我用黃色箭頭指示的即為AppWidget,一些用戶使用比較頻繁的程序,可以做成AppWidget,這樣能方便地使用。典型的程序有時鐘、天氣、音樂播放器等。AppWidget 是Android 系統(tǒng)應(yīng)用開發(fā)層面的一部分,有著特殊用途,使用得當(dāng)?shù)幕?,的確會為app 增色不少,它的工作原理是把一個進(jìn)程的控件嵌入到別外一個進(jìn)程的窗口里的一種方法。長按桌面空白處,會出現(xiàn)一個 AppWidget 的文件夾,在里面找到相應(yīng)的 AppWidget ,長按拖出,即可將 AppWidget 添加到桌面,
如何開發(fā)AppWidget
AppWidget 是通過 BroadCastReceiver 的形式進(jìn)行控制的,開發(fā) AppWidget 的主要類為 AppWidgetProvider, 該類繼承自 BroadCastReceiver。為了實(shí)現(xiàn)桌面小部件,開發(fā)者只要開發(fā)一個繼承自 AppWidgetProvider 的子類,并重寫它的 onUpdate() 方法即可。重寫該方法,一般來說可按如下幾個步驟進(jìn)行:
1、創(chuàng)建一個 RemoteViews 對象,這個對象加載時指定了桌面小部件的界面布局文件。
2、設(shè)置 RemoteViews 創(chuàng)建時加載的布局文件中各個元素的屬性。
3、創(chuàng)建一個 ComponentName 對象
4、調(diào)用 AppWidgetManager 更新桌面小部件。
下面來看一個實(shí)際的例子,用 Android Studio 自動生成的例子來說。(注:我用的是最新版的 AS 2.2.3,下面簡稱 AS。)
新建了一個 HelloWorld 項(xiàng)目,然后新建一個 AppWidget ,命名為 MyAppWidgetProvider,按默認(rèn)下一步,就完成了一個最簡單的AppWidget的開發(fā)。運(yùn)行程序之后,將小部件添加到桌面。操作步驟和默認(rèn)效果如下:

我們看看 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默認(rèn)幫我們重寫 onUpdate() 方法,遍歷 appWidgetIds, 調(diào)用了 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ù)傳應(yīng)用程序包名,第二個參數(shù)指定了,RemoteViews 加載的布局文件。這一行對應(yīng)上面步驟中說的第一點(diǎn)??梢钥吹皆?res/layout/ 目錄下面 AS 自動生成了一個 my_app_widget_provider.xml 文件,內(nèi)容如下:
<RelativeLayout xmlns: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 就行了,聰明的你可能開始想自定義小部件的樣式了,添加功能強(qiáng)大外觀漂亮逼格高的自定義控件了,很遺憾,不可以。小部件布局文件可以添加的組件是有限制的,詳細(xì)內(nèi)容在下文介紹RemoteViews 時再說。
第三行,views.setTextViewText(R.id.appwidget_text, widgetText);
將第一行聲明的字符串賦值給上面布局文件中的 TextView,注意這里賦值時,指定TextView的 id,要對應(yīng)起來。這一行對于了上面步驟中的第二點(diǎn)。
第四行,appWidgetManager.updateAppWidget(appWidgetId, views);
這里調(diào)用了 appWidgetManager.updateAppWidget() 方法,更新小部件。這一行對應(yīng)了上面步驟中的第四點(diǎn)。
這時,你可能有疑問了,上面明明說了四個步驟,其中第三步,創(chuàng)建一個 ComponentName 對象,明明就不需要。的確,這個例子中也沒有用到。如果我們手敲第四步代碼,AS的智能提示會告訴你,appWidgetManager.updateAppWidget() 有三個重載的方法。源碼中三個方法沒有寫在一起,為了方便,這里我復(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. |
|||||||||||||
這個三個方法都接收兩個參數(shù),第二個參數(shù)都是 RemoteViews 對象。其中第一個方法的第一個參數(shù)就是 ComponentName 對象,更新所有的 AppWidgetProvider 提供的所有的 AppWidget 實(shí)例,第二個方法時更新明確指定 Id 的 AppWidget 的對象集,第三個方法,更新明確指定 Id 的某個 AppWidget 對象。所以一般我們使用第一個方法,針對所有的 AppWidget 對象,我們也可以根據(jù)需要選擇性地去更新。
到這里,所有步驟都結(jié)束了,就完了?還沒。前面說了,自定義的 MyAppWidgetProvider 繼承自 AppWidgetProvider,而 AppWidgetProvider 又是繼承自 BroadCastReceiver,
所以說 MyAppWidgetProvider 本質(zhì)上是一個廣播接受者,屬于四大組件之一,需要我們的清單文件中注冊。打開AndroidManifest.xml文件可以看到,的確是注冊了小部件的,內(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>
上面代碼中有一個 Action,這個 Action 必須要加,且不能更改,屬于系統(tǒng)規(guī)范,是作為小部件的標(biāo)識而存在的。如果不加,這個 Receiver 就不會出現(xiàn)在小部件列表里面。然后看到小部件指定了 @xml/my_app_widget_provider_info 作為meta-data,細(xì)心的你發(fā)現(xiàn)了,在 res/ 目錄下面建立了一個 xml 文件夾,下面新建了一個 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" 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文檔。
到這里,上面這個極簡單的小部件開發(fā)過程就真的結(jié)束了。為了開發(fā)出更強(qiáng)大一點(diǎn)小部件,我們還需要進(jìn)一步了解 RemoteViews 和 AppWidgetProvider。
AppWidget的妝容——RemoteViews
下面簡單說說 RemoteViews 相關(guān)的幾個類。
1.1 RemoteViews
RemoteViews,從字面意思理解為它是一個遠(yuǎn)程視圖。是一種遠(yuǎn)程的 View,它在其它進(jìn)程中顯示,卻可以在另一個進(jìn)程中更新。RemoteViews 在Android中的使用場景主要有:自定義通知欄和桌面小部件。
在RemoteViews 的構(gòu)造函數(shù)中,第二個參數(shù)接收一個 layout 文件來確定 RemoteViews 的視圖;然后,我們調(diào)用RemoteViews 中的 set 方法對 layout 中的各個組件進(jìn)行設(shè)置,例如,可以調(diào)用 setTextViewText() 來設(shè)置 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() 方法來為小部件的子視圖設(shè)置屬性。具體可以參考 API 文檔。
1.2 RemoteViewsService
RemoteViewsService,是管理RemoteViews的服務(wù)。一般,當(dāng)AppWidget 中包含 GridView、ListView、StackView 等集合視圖時,才需要使用RemoteViewsService來進(jìn)行更新、管理。RemoteViewsService 更新集合視圖的一般步驟是:
(01) 通過 setRemoteAdapter() 方法來設(shè)置 RemoteViews 對應(yīng) RemoteViewsService 。
(02) 之后在 RemoteViewsService 中,實(shí)現(xiàn) RemoteViewsFactory 接口。然后,在 RemoteViewsFactory 接口中對集合視圖的各個子項(xiàng)進(jìn)行設(shè)置,例如 ListView 中的每一Item。
1.3 RemoteViewsFactory
通過RemoteViewsService中的介紹,我們知道RemoteViewsService是通過 RemoteViewsFactory來具體管理layout中集合視圖的,RemoteViewsFactory是RemoteViewsService中的一個內(nèi)部接口。RemoteViewsFactory提供了一系列的方法管理集合視圖中的每一項(xiàng)。例如:
RemoteViews getViewAt(int position)
通過getViewAt()來獲取“集合視圖”中的第position項(xiàng)的視圖,視圖是以RemoteViews的對象返回的。
int getCount()
通過getCount()來獲取“集合視圖”中所有子項(xiàng)的總數(shù)。
AppWidget的美貌——AppWidgetProvider
我們說一位女同事漂亮,除了因?yàn)樗┑囊路?、化的妝漂亮以外,我想最主要的原因還是她本人長的漂亮吧。同樣,小部件之所以有附著在桌面,跨進(jìn)程更新 View 的能力,主要是因?yàn)锳ppWidgetProvider 是一個廣播接收者。
我們發(fā)現(xiàn),上面的例子中,AS 幫我們自動生成的代碼中,除了 onUpdate() 方法被我們重寫了,還有重寫 onEnable() 和 onDisable() 兩個方法,但都是空實(shí)現(xiàn),這兩個方法什么時候會被調(diào)用?還有,我們說自定義的 MyAppWidgetProvider,繼承自 AppWidgetProvider,而 MyAppWidgetProvider 又是BroadCastReceiver 的子類,而我們卻沒有向?qū)懗R?guī)廣播接收者一樣重寫 onReceiver() 方法?下面跟進(jìn)去 AppWidgetProvider 源碼,一探究竟。
這個類代碼并不多,其實(shí),AppWidgetProvider 出去構(gòu)造方法外,總共只有下面這些方法:
onEnable() :當(dāng)小部件第一次被添加到桌面時回調(diào)該方法,可添加多次,但只在第一次調(diào)用。對用廣播的 Action 為 ACTION_APPWIDGET_ENABLE。
onUpdate(): 當(dāng)小部件被添加時或者每次小部件更新時都會調(diào)用一次該方法,配置文件中配置小部件的更新周期 updatePeriodMillis,每次更新都會調(diào)用。對應(yīng)廣播 Action 為:ACTION_APPWIDGET_UPDATE 和 ACTION_APPWIDGET_RESTORED 。
onDisabled(): 當(dāng)最后一個該類型的小部件從桌面移除時調(diào)用,對應(yīng)的廣播的 Action 為 ACTION_APPWIDGET_DISABLED。
onDeleted(): 每刪除一個小部件就調(diào)用一次。對應(yīng)的廣播的 Action 為: ACTION_APPWIDGET_DELETED 。
onRestored(): 當(dāng)小部件從備份中還原,或者恢復(fù)設(shè)置的時候,會調(diào)用,實(shí)際用的比較少。對應(yīng)廣播的 Action 為 ACTION_APPWIDGET_RESTORED。
onAppWidgetOptionsChanged(): 當(dāng)小部件布局發(fā)生更改的時候調(diào)用。對應(yīng)廣播的 Action 為 ACTION_APPWIDGET_OPTIONS_CHANGED。
最后就是 onReceive() 方法了,AppWidgetProvider 重寫了該方法,用于分發(fā)具體的時間給上述的方法??纯丛创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í)
下面再自己寫個例子,學(xué)習(xí) RemoteViews 中的其它知識點(diǎn),這個例子中小部件布局中用到 button 和 listview。上代碼:
小部件的布局文件 mul_app_widget_provider.xml 如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns: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" 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: 對應(yīng)啟動 ListViewService(RemoteViewsService) 的intent
// (02) setRemoteAdapter: 設(shè)置 ListView 的適配器
// 通過setRemoteAdapter將 ListView 和ListViewService關(guān)聯(lián)起來,
// 以達(dá)到通過 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模板
// 說明:“集合控件(如GridView、ListView、StackView等)”中包含很多子元素,如GridView包含很多格子。
// 它們不能像普通的按鈕一樣通過 setOnClickPendingIntent 設(shè)置點(diǎn)擊事件,必須先通過兩步。
// (01) 通過 setPendingIntentTemplate 設(shè)置 “intent模板”,這是比不可少的!
// (02) 然后在處理該“集合控件”的RemoteViewsFactory類的getViewAt()接口中 通過 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:
package 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 在小部件中的用法:
package 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;
}
/* 在更新界面的時候如果耗時就會顯示 正在加載... 的默認(rèn)字樣,但是你可以更改這個界面
* 如果返回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" 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 顯示小機(jī)器人,下面有一個 Button ,右邊有一個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ù)雜一些了。首先需要自定義一個類繼承自 RemoteViewsServices ,并重寫 onGetViewFactory 方法,返回 RemoteViewsService.RemoteViewsFactory 接口的對象。這里定義了一個內(nèi)部類實(shí)現(xiàn)該接口,需要重寫多個方法,與 ListView 的多布局適配很類似。重點(diǎn)方法是
public RemoteViews getViewAt(int position){}
這個方法中指定了 ListView 的每一個 item 的布局以及內(nèi)容,同時通過 setOnClickFillInIntent() 或者 setOnClickPendingIntent() 給 item 設(shè)置點(diǎn)擊事件。這里我實(shí)現(xiàn)的點(diǎn)擊 item,替換左邊的 ImageView 的圖片。重寫了 MulAppWidgetProvider 類的 onReceiver 方法,處理替換圖片的邏輯。
程序運(yùn)行效果如下圖:

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android用MVP實(shí)現(xiàn)一個簡單的類淘寶訂單頁面的示例
本篇文章主要介紹了Android用MVP實(shí)現(xiàn)一個簡單的類淘寶訂單頁面的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-11-11
Android自定義ViewGroup嵌套與交互實(shí)現(xiàn)幕布全屏滾動
這篇文章主要為大家介紹了Android自定義ViewGroup嵌套與交互實(shí)現(xiàn)幕布全屏滾動效果示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
使用genymotion訪問本地上Tomcat上數(shù)據(jù)的方法
下面小編就為大家?guī)硪黄褂胓enymotion訪問本地上Tomcat上數(shù)據(jù)的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03
android文件操作——讀取assets和raw文件下的內(nèi)容
本篇文章主要介紹了android文件操作——讀取assets和raw文件下的內(nèi)容,并附簡單實(shí)例代碼,需要的朋友可以參考下。2016-10-10
Android系列之Intent傳遞對象的幾種實(shí)例方法
Android系列之Intent傳遞對象的幾種實(shí)例方法,需要的朋友可以參考一下2013-05-05
Android使用NestedScrollView?內(nèi)嵌RecycleView滑動沖突問題解決
這篇文章主要介紹了Android使用NestedScrollView?內(nèi)嵌RecycleView滑動沖突問題解決,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,感興趣的小伙伴可以參考一下2022-06-06
Android中傳遞對象的三種方法的實(shí)現(xiàn)
本篇文章主要介紹了Android中傳遞對象的三種方法的實(shí)現(xiàn),可以通過Bundle、Intent或者JSON字符串,有興趣的可以了解一下。2017-02-02
Android TextView漸變顏色和方向及動畫效果的設(shè)置詳解
TextView的在安卓中可以理解為一個文本視圖控件,Android的視圖控件的基類是View類,可以理解的TextView是View的子類。我們通常在.XML布局文件中會為文本視圖控件指定各種屬性來設(shè)置它的樣式,今天我們要講的當(dāng)然不是傳統(tǒng)常見的那種,將會帶有漸變顏色和方向及動畫效果2021-11-11

