AppWidget應用小部件詳解(二)
在上一篇的
AppWidget應用小部件詳解(一)中,介紹瞭如何自定義實現一個簡單的App Widget應用小部件的步驟,而在本篇中將繼續介紹自定義App Widget的創建,這次將介紹在應用小部件中顯示一個列表容器視圖,比如ListView、GridView、StackView、AdapterViewFlipper。接下來就實現一個基於該模式的
圖片瀏覽的App
Widget。
其實創建應用小部件的步驟都一樣,只是在呈現小部件內容的處理方式上面不同罷了,現在就開始實現該圖片瀏覽的小部件(基於android系統4.1)。
1、首先創建AppWidgetProviderInfo對象,即在res/xml目錄下創建一個appwidget_provider_image.xml,該文件的內容對於所有的App Widget基本上大體相同,其代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="93dp"
android:minHeight="93dp"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/appwidget_provider_image" />
2、接着創建App Widget的佈局文件,用於顯示App Widget,該文件中有兩個顯示的view,StackView就是圖片瀏覽的主要view,ImageView只是當StackView中沒有內容時纔會顯示出來,其文件如下:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<StackView
android:id="@+id/stack_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:loopViews="true" />
<ImageView
android:id="@+id/empty_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/stack_img_empty"
android:contentDescription="@string/app_name"/>
</FrameLayout>
3、然後創建AppWidgetProvider的實現類StackViewAppWidgetProvider,用來接收App Widget發佈的廣播,根據廣播進行相關的創建、更新App Widget等操作,與之前數字時鐘的AppWidgetProvider實現類有所不同,StackView中顯示的數據不能在當前的StackViewAppWidgetProvider類中去設置,必須通過設置RemoteAdapter來啓動一個提供數據的StackRemoteViewsService服務;並且如果需要使StackView中的每一個子view都有自己獨立的行爲動作,就必須首先在承載該StackView的RemoteViews中設置一個PenddingIntentTemplate,對於觸發的子view就可以通過這個PenddingIntentTemplate去執行其中的Intent,然後在StackRemoteViewsFactory類的getViewAt方法中爲每一個子view設置一個FillInIntent,從而攜帶不同的數據,這樣就可以在每一個子view上觸發事件,通過相應的處理類進行處理,其核心代碼如下:
public static final String TOAST_ACTION = "com.android.stackviewappwidget.TOAST_ACTION";
public static final String EXTRA_ITEM = "com.android.stackviewappwidget.EXTRA_ITEM";
/**
* 廣播接收處理
* 當intent的 action爲TOAST_ACTION時,就進行相應的界面更新處理?
* (點擊stackview的哪一個子view就將其顯示在最前面)
*/
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if(intent.getAction().equals(TOAST_ACTION))
{
// 創建AppWidgetManager對象
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
// 獲得應用小部件的id
int appWidgetId = intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
// 獲取當前點擊的stackview子view的位置
int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
// 根據view的位置進行界面的更新,將該view顯示在stackview的最前面
// 創建RemoteViews對象
RemoteViews views = createRemoteViews(context, appWidgetId);
// 顯示當前點擊的子view
views.setDisplayedChild(R.id.stack_image, viewIndex);
// 更新AppWidget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
for(int i=0;i<appWidgetIds.length;i++)
{
// 構造展示圖片瀏覽的RemoteViews對象
RemoteViews views = createRemoteViews(context, appWidgetIds[i]);
// 更新當前的App Widget
appWidgetManager.updateAppWidget(appWidgetIds[i], views);
}
}
/**
* 構造展示圖片瀏覽的RemoteViews對象
* @return
*/
private RemoteViews createRemoteViews(Context context, int appWidgetId)
{
// 創建RemoteViews對象,用於widget佈局
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_image);
// 創建啓動v
Intent intent = new Intent(context, StackRemoteViewsService.class);
// 將app widget id 添加到intent extras中
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
// 給views設置RemoteViews適配器
// 該適配器通過intent來連接RemoteViewsService,獲得展示的數據
views.setRemoteAdapter(R.id.stack_image, intent);
// 如果StackView的數據爲空,則顯示empty_image視圖
views.setEmptyView(R.id.stack_image, R.id.empty_image);
/**
* 創建PendingIntent模版:給子view添加獨立的觸發事件
* 一旦點擊子view之後就會發送一個帶TOAST_ACTION的廣播
*/
// 創建Intent對象,用來啓動StackViewAppWidgetProvider
Intent toastIntent = new Intent(context, StackViewAppWidgetProvider.class);
// 設置Intent的Action,啓動相應組件的標識
toastIntent.setAction(TOAST_ACTION);
// 將app widget id 添加到intent extras中
toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
toastIntent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
// 根據toastIntent創建一個啓動廣播接收器的PendingIntent模版
PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// 將PendingIntent模版添加到RemoteViews中
views.setPendingIntentTemplate(R.id.stack_image, toastPendingIntent);
return views;
}
4、創建爲RemoteViewsAdapter提供數據的StackRemoteViewsService服務,該服務中的核心就是RemoteViewsFactory的實現類StackRemoteViewsFactory,該類其實類似於一個自定義的BaseAdapter,爲了讓StackRemoteViewsFactory能夠生成顯示一個view就必須實現幾個核心的方法getCount、getViewAt、onCreate等。其核心代碼如下:
public class StackRemoteViewsService extends RemoteViewsService {
// 創建RemoteViewsFactory對象
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
}
/**
* StackView中RemoteViews構造器
* @author Administrator
*
*/
class StackRemoteViewsFactory implements RemoteViewsFactory
{
private Context mContext;
// stack圖片
private int[] stackImages;
public StackRemoteViewsFactory(Context applicationContext, Intent intent) {
this.mContext = applicationContext;
}
@Override
public int getCount() {
return stackImages.length;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public RemoteViews getLoadingView() {
return null;
}
@Override
public RemoteViews getViewAt(int position) {
// 創建RemoteViews對象
RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.appwidget_provider_item);
// 設置RemoteViews中顯示的圖片信息
views.setImageViewResource(R.id.image_item, stackImages[position]);
// 創建Intent對象,用來爲每一個子view攜帶相關的數據
Intent fillInIntent = new Intent();
fillInIntent.putExtra(StackViewAppWidgetProvider.EXTRA_ITEM, position);
// 通過設置setOnClickFillInIntent方法,結合PengdingIntentTemplate就可以實現子view的觸發事件
views.setOnClickFillInIntent(R.id.image_item, fillInIntent);
return views;
}
@Override
public int getViewTypeCount() {
return 0;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public void onCreate() {
// 初始化stack圖片
stackImages = new int[]{R.drawable.stack_img_1, R.drawable.stack_img_2, R.drawable.stack_img_3,
R.drawable.stack_img_4, R.drawable.stack_img_5, R.drawable.stack_img_6,
R.drawable.stack_img_7};
}
@Override
public void onDataSetChanged() {
}
@Override
public void onDestroy() {
}
}
}
5、最後將StackViewAppWidgetProvider類以及StackRemoteViewsService註冊到AndroidManifest.xml配置文件中,其文件如下:
<!-- 處理StackViewAppWidget的廣播接收器 -->
<receiver android:name=".StackViewAppWidgetProvider"
android:label="@string/image_view"
android:icon="@drawable/image_view">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.android.stackviewappwidget.TOAST_ACTION" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/appwidget_provider_image"/>
</receiver>
<!-- 提供集合數據的服務:用於綁定remoteviews視圖 -->
<service android:name=".StackRemoteViewsService"
android:permission="android.permission.BIND_REMOTEVIEWS"/>
6、通過以上幾步,自定義圖片瀏覽的App Widget就創建成功了,由於是運行在android4.1系統上面的,將應用小部件添加到手機桌面的方式和將應用快捷鍵添加到手機桌面的方式一樣,其步驟以及運行結果如下所示:
點擊手機屏幕下方的菜單按鈕,進入所有顯示應用界面:
通過向右滑動屏幕,一般到最後一個屏幕的界面就可以找到我們自定義的圖片瀏覽的App Widget:
通過長按該自定義的圖片瀏覽的App Widget的圖標,就可以將其拖到手機桌面進行顯示: