HoneyComb3.0技术系列之AppWidget(RemoteViewService)

1. 概述:

 

    在HoneyComb3.0中AppWidget上可以添加更多的组件,如:ListView,GridView,StackView和ViewFlipper等集合组件。它提供了一套新的

 

    集合组件渲染机制RemoteViewService,它继承自Service,向外提供渲染ListView,GridView或StackView等集合组件的Factory,这个Factory

 

    类似Adapter向这些集合组件提供数据。在AppWidgetProviderInfo类中(<appwidget-provider>标签)新增加了两个属性,一个是

 

    previewImage,这个属性指定一张图片,这个图片显示在添加AppWidget的Picker界面中,拖动这张图片到Launcher的WorkSpace后就向其中

 

    添加图片所对应的AppWidget,另一个是autoAdvanceViewId,这个属性指定自动更新的ViewID,我指定StackView的ID有效果(View之间不

 

    停的滑动),但是指定的ListView或GridView的ID没有任何效果。

 

2. 整体效果图:

 

    (1)AppWidget的Picker效果图,如下:

            HoneyComb3.0技术系列之AppWidget(RemoteViewService)_第1张图片

    (2)AppWidget效果图,如下:

            HoneyComb3.0技术系列之AppWidget(RemoteViewService)_第2张图片

3. AppWidget的代码实现,如下:

 

    (1)AndroidManifest.xml配置文件,如下:<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.weatherlistwidget"> <application android:label="Weather Widget Sample"> <receiver android:name="WeatherWidgetProvider"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/widgetinfo" /> </receiver> <service android:name="WeatherWidgetService" android:permission="android.permission.BIND_REMOTEVIEWS" android:exported="false" /> <provider android:name="WeatherDataProvider" android:authorities="com.example.android.weatherlistwidget.provider" /> </application> <uses-sdk android:minSdkVersion="11" /> </manifest>

    (2)res/xml/目录中AppWidget的配置文件,如下:<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="222dip" android:minHeight="222dip" android:updatePeriodMillis="1800000" android:initialLayout="@layout/widget_layout" android:previewImage="@drawable/preview" > </appwidget-provider>

    (3)res/layout目录中AppWidget的总布局,如下:<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="294dp" android:layout_height="match_parent" android:orientation="vertical"> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/header" android:layout_width="match_parent" android:layout_height="wrap_content" android:src="@drawable/header" /> <ImageButton android:id="@+id/refresh" android:layout_width="56dp" android:layout_height="39dp" android:layout_marginLeft="222dp" android:layout_marginTop="20dp" android:background="@drawable/refresh_button" /> </FrameLayout> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:layout_gravity="center" android:background="@drawable/body"> <ListView android:id="@+id/weather_list" android:layout_width="match_parent" android:layout_height="match_parent" /> <TextView android:id="@+id/empty_view" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:visibility="gone" android:textColor="#ffffff" android:text="@string/empty_view_text" android:textSize="20sp" /> </FrameLayout> <ImageView android:id="@+id/footer" android:layout_width="match_parent" android:layout_height="wrap_content" android:src="@drawable/footer" /> </LinearLayout> 

    (4)ContentProvider类,如下:package com.example.android.weatherlistwidget; import java.util.ArrayList; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; class WeatherDataPoint { String city; int degrees; WeatherDataPoint(String c, int d) { city = c; degrees = d; } } public class WeatherDataProvider extends ContentProvider { public static final Uri CONTENT_URI = Uri.parse("content://com.example.android.weatherlistwidget.provider"); public static class Columns { public static final String ID = "_id"; public static final String CITY = "city"; public static final String TEMPERATURE = "temperature"; } private static final ArrayList<WeatherDataPoint> sData = new ArrayList<WeatherDataPoint>(); /** * 在onCreate()时初始化数据。 */ @Override public boolean onCreate() { sData.add(new WeatherDataPoint("San Francisco", 13)); sData.add(new WeatherDataPoint("New York", 1)); sData.add(new WeatherDataPoint("Seattle", 7)); sData.add(new WeatherDataPoint("Boston", 4)); sData.add(new WeatherDataPoint("Miami", 22)); sData.add(new WeatherDataPoint("Toronto", -10)); sData.add(new WeatherDataPoint("Calgary", -13)); sData.add(new WeatherDataPoint("Tokyo", 8)); sData.add(new WeatherDataPoint("Kyoto", 11)); sData.add(new WeatherDataPoint("London", -1)); sData.add(new WeatherDataPoint("Nomanisan", 27)); return true; } @Override public synchronized Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { assert(uri.getPathSegments().isEmpty()); /** * 创建MatrixCursor,并向MatrixCursor中添加数据,返回。 */ final MatrixCursor c = new MatrixCursor(new String[]{ Columns.ID, Columns.CITY, Columns.TEMPERATURE }); for (int i = 0; i < sData.size(); ++i) { final WeatherDataPoint data = sData.get(i); c.addRow(new Object[]{ new Integer(i), data.city, new Integer(data.degrees) }); } return c; } @Override public String getType(Uri uri) { return "vnd.android.cursor.dir/vnd.weatherlistwidget.citytemperature"; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public synchronized int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { assert(uri.getPathSegments().size() == 1); final int index = Integer.parseInt(uri.getPathSegments().get(0)); assert(0 <= index && index < sData.size()); final WeatherDataPoint data = sData.get(index); data.degrees = values.getAsInteger(Columns.TEMPERATURE); /** * 提醒ContentObserver数据改变。 */ getContext().getContentResolver().notifyChange(uri, null); return 1; } }

    (5)AppWidget类,如下:package com.example.android.weatherlistwidget; import java.util.Random; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; import android.widget.RemoteViews; import android.widget.Toast; class WeatherDataProviderObserver extends ContentObserver { private AppWidgetManager mAppWidgetManager; private ComponentName mComponentName; WeatherDataProviderObserver(AppWidgetManager mgr, ComponentName cn, Handler h) { super(h); mAppWidgetManager = mgr; mComponentName = cn; } /** * ContentProvider的内容改变后会通过AppWidget组件中的ListView重新渲染数据。这里会调用RemoteViewService中RemoteViewsFactory的onDataSetChanged方法。 */ @Override public void onChange(boolean selfChange) { mAppWidgetManager.notifyAppWidgetViewDataChanged(mAppWidgetManager.getAppWidgetIds(mComponentName), R.id.weather_list); } } public class WeatherWidgetProvider extends AppWidgetProvider { public static String CLICK_ACTION = "com.example.android.weatherlistwidget.CLICK"; public static String REFRESH_ACTION = "com.example.android.weatherlistwidget.REFRESH"; public static String EXTRA_CITY_ID = "com.example.android.weatherlistwidget.city"; private static HandlerThread sWorkerThread; private static Handler sWorkerQueue; private static WeatherDataProviderObserver sDataObserver; public WeatherWidgetProvider() { sWorkerThread = new HandlerThread("WeatherWidgetProvider-worker"); sWorkerThread.start(); sWorkerQueue = new Handler(sWorkerThread.getLooper()); } @Override public void onEnabled(Context context) { final ContentResolver r = context.getContentResolver(); /** * 在发出AppWidget的Enable广播后向WeatherDataProvider注册WeatherDataProviderObserver(ContentObserver)。 */ if (sDataObserver == null) { final AppWidgetManager mgr = AppWidgetManager.getInstance(context); final ComponentName cn = new ComponentName(context, WeatherWidgetProvider.class); sDataObserver = new WeatherDataProviderObserver(mgr, cn, sWorkerQueue); r.registerContentObserver(WeatherDataProvider.CONTENT_URI, true, sDataObserver); } } @Override public void onReceive(Context ctx, Intent intent) { final String action = intent.getAction(); if (action.equals(REFRESH_ACTION)) { final Context context = ctx; sWorkerQueue.removeMessages(0); sWorkerQueue.post(new Runnable() { /** * 单击刷新按钮后会更新数据。 */ @Override public void run() { final ContentResolver r = context.getContentResolver(); final Cursor c = r.query(WeatherDataProvider.CONTENT_URI, null, null, null, null); final int count = c.getCount(); final int maxDegrees = 96; r.unregisterContentObserver(sDataObserver); for (int i = 0; i < count; ++i) { final Uri uri = ContentUris.withAppendedId(WeatherDataProvider.CONTENT_URI, i); final ContentValues values = new ContentValues(); values.put(WeatherDataProvider.Columns.TEMPERATURE, new Random().nextInt(maxDegrees)); r.update(uri, values, null, null); } r.registerContentObserver(WeatherDataProvider.CONTENT_URI, true, sDataObserver); final AppWidgetManager mgr = AppWidgetManager.getInstance(context); final ComponentName cn = new ComponentName(context, WeatherWidgetProvider.class); /** * 这句话会调用RemoteViewSerivce中RemoteViewsFactory的onDataSetChanged()方法。 */ mgr.notifyAppWidgetViewDataChanged(mgr.getAppWidgetIds(cn), R.id.weather_list); } }); } else if (action.equals(CLICK_ACTION)) { /** * 单击ListView中的某一项会显示一个Toast提示。 */ final String city = intent.getStringExtra(EXTRA_CITY_ID); final String formatStr = ctx.getResources().getString(R.string.toast_format_string); Toast.makeText(ctx, String.format(formatStr, city), Toast.LENGTH_SHORT).show(); } super.onReceive(ctx, intent); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { for (int i = 0; i < appWidgetIds.length; ++i) { /** * 创建请求Service的Intent对象。 */ final Intent intent = new Intent(context, WeatherWidgetService.class); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]); intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); /** * 创建RemoteViews对象,在其中的布局中存在一个ListView组件widget_layout。 */ final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout); /** * 组件ListView组件weather_list设置RemoteViewService渲染ListView. */ rv.setRemoteAdapter(appWidgetIds[i], R.id.weather_list, intent); /** * 如果weather_list为空则显示empty_view。 */ rv.setEmptyView(R.id.weather_list, R.id.empty_view); /** * 点击ListView某一项时会显示一个Toast提示。 */ final Intent onClickIntent = new Intent(context, WeatherWidgetProvider.class); onClickIntent.setAction(WeatherWidgetProvider.CLICK_ACTION); onClickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]); onClickIntent.setData(Uri.parse(onClickIntent.toUri(Intent.URI_INTENT_SCHEME))); final PendingIntent onClickPendingIntent = PendingIntent.getBroadcast(context, 0, onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT); rv.setPendingIntentTemplate(R.id.weather_list, onClickPendingIntent); /** * 点击刷新按钮时刷新ListView数据。 */ final Intent refreshIntent = new Intent(context, WeatherWidgetProvider.class); refreshIntent.setAction(WeatherWidgetProvider.REFRESH_ACTION); final PendingIntent refreshPendingIntent = PendingIntent.getBroadcast(context, 0, refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT); rv.setOnClickPendingIntent(R.id.refresh, refreshPendingIntent); /** * 更新AppWidget组件。 */ appWidgetManager.updateAppWidget(appWidgetIds[i], rv); } super.onUpdate(context, appWidgetManager, appWidgetIds); } }

 

    (6)RemoteViewsService渲染器,如下:package com.example.android.weatherlistwidget; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; import android.widget.RemoteViews; import android.widget.RemoteViewsService; public class WeatherWidgetService extends RemoteViewsService { @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return new StackRemoteViewsFactory(this.getApplicationContext(), intent); } } /** * 这个Factory就类拟一个Adapter,可以为AppWidget中的集合组件设置数据。 */ class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { private Context mContext; private Cursor mCursor; public StackRemoteViewsFactory(Context context, Intent intent) { mContext = context; } public void onCreate() { } public void onDestroy() { if (mCursor != null) { mCursor.close(); } } /** * 获取Cursor中的数据个数。 */ public int getCount() { return mCursor.getCount(); } /** * 获取ListView中的每一个条目View. */ public RemoteViews getViewAt(int position) { String city = "Unknown City"; int temp = 0; if (mCursor.moveToPosition(position)) { final int cityColIndex = mCursor.getColumnIndex(WeatherDataProvider.Columns.CITY); final int tempColIndex = mCursor.getColumnIndex( WeatherDataProvider.Columns.TEMPERATURE); city = mCursor.getString(cityColIndex); temp = mCursor.getInt(tempColIndex); } final String formatStr = mContext.getResources().getString(R.string.item_format_string); final int itemId = (position % 2 == 0 ? R.layout.light_widget_item : R.layout.dark_widget_item); RemoteViews rv = new RemoteViews(mContext.getPackageName(), itemId); rv.setTextViewText(R.id.widget_item, String.format(formatStr, temp, city)); final Intent fillInIntent = new Intent(); final Bundle extras = new Bundle(); extras.putString(WeatherWidgetProvider.EXTRA_CITY_ID, city); fillInIntent.putExtras(extras); rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent); return rv; } public RemoteViews getLoadingView() { return null; } public int getViewTypeCount() { return 2; } public long getItemId(int position) { return position; } public boolean hasStableIds() { return true; } /** * 在AppWidget被第一次加载到屏幕时会自动调用此方法或者调用AppWidgetManager.notifyAppWidgetViewDataChanged()方法也会调用此方法。 */ public void onDataSetChanged() { if (mCursor != null) { mCursor.close(); } mCursor = mContext.getContentResolver().query(WeatherDataProvider.CONTENT_URI, null, null, null, null); } }

你可能感兴趣的:(HoneyComb3.0技术系列之AppWidget(RemoteViewService))