首页 > 代码库 > HoneyComb3.0技术系列之AppWidget(RemoteViewService)
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效果图,如下:
(2)AppWidget效果图,如下:
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=http://www.mamicode.com/"@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=http://www.mamicode.com/"@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);
- }
- }
原文:http://blog.csdn.net/mayingcai1987/article/details/6264213
HoneyComb3.0技术系列之AppWidget(RemoteViewService)