首页 > 代码库 > 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效果图,如下:

            AppWidget1

    (2)AppWidget效果图,如下:

            AppWidget2

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

 

    (1)AndroidManifest.xml配置文件,如下:

[java] view plaincopy
 
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.example.android.weatherlistwidget">  
  4.       
  5.     <application android:label="Weather Widget Sample">  
  6.       
  7.         <receiver android:name="WeatherWidgetProvider">  
  8.             <intent-filter>  
  9.                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />  
  10.             </intent-filter>  
  11.             <meta-data android:name="android.appwidget.provider"  
  12.                     android:resource="@xml/widgetinfo" />  
  13.         </receiver>  
  14.         <service android:name="WeatherWidgetService"  
  15.             android:permission="android.permission.BIND_REMOTEVIEWS"  
  16.             android:exported="false" />  
  17.         <provider android:name="WeatherDataProvider"  
  18.               android:authorities="com.example.android.weatherlistwidget.provider" />  
  19.                 
  20.     </application>  
  21.       
  22.     <uses-sdk android:minSdkVersion="11" />  
  23.       
  24. </manifest>  

 

    (2)res/xml/目录中AppWidget的配置文件,如下:

[java] view plaincopy
 
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <appwidget-provider  
  3.   xmlns:android="http://schemas.android.com/apk/res/android"  
  4.   android:minWidth="222dip"  
  5.   android:minHeight="222dip"  
  6.   android:updatePeriodMillis="1800000"  
  7.   android:initialLayout="@layout/widget_layout"  
  8.   android:previewImage="@drawable/preview"  
  9.   >  
  10. </appwidget-provider>  

 

    (3)res/layout目录中AppWidget的总布局,如下:

[java] view plaincopy
 
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="294dp"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical">  
  6.       
  7.     <FrameLayout  
  8.         android:layout_width="match_parent"  
  9.         android:layout_height="wrap_content">  
  10.         <ImageView  
  11.             android:id="@+id/header"  
  12.             android:layout_width="match_parent"  
  13.             android:layout_height="wrap_content"  
  14.             android:src=http://www.mamicode.com/"@drawable/header" />  
  15.         <ImageButton  
  16.             android:id="@+id/refresh"  
  17.             android:layout_width="56dp"  
  18.             android:layout_height="39dp"  
  19.             android:layout_marginLeft="222dp"  
  20.             android:layout_marginTop="20dp"  
  21.             android:background="@drawable/refresh_button" />  
  22.     </FrameLayout>  
  23.       
  24.     <FrameLayout  
  25.         android:layout_width="match_parent"  
  26.         android:layout_height="match_parent"  
  27.         android:layout_weight="1"  
  28.         android:layout_gravity="center"  
  29.         android:background="@drawable/body">  
  30.         <ListView  
  31.             android:id="@+id/weather_list"  
  32.             android:layout_width="match_parent"  
  33.             android:layout_height="match_parent" />  
  34.         <TextView  
  35.             android:id="@+id/empty_view"  
  36.             android:layout_width="match_parent"  
  37.             android:layout_height="match_parent"  
  38.             android:gravity="center"  
  39.             android:visibility="gone"  
  40.             android:textColor="#ffffff"  
  41.             android:text="@string/empty_view_text"  
  42.             android:textSize="20sp" />  
  43.     </FrameLayout>  
  44.       
  45.     <ImageView  
  46.         android:id="@+id/footer"  
  47.         android:layout_width="match_parent"  
  48.         android:layout_height="wrap_content"  
  49.         android:src=http://www.mamicode.com/"@drawable/footer" />  
  50. </LinearLayout>   

 

    (4)ContentProvider类,如下:

[java] view plaincopy
 
  1. package com.example.android.weatherlistwidget;  
  2. import java.util.ArrayList;  
  3. import android.content.ContentProvider;  
  4. import android.content.ContentValues;  
  5. import android.database.Cursor;  
  6. import android.database.MatrixCursor;  
  7. import android.net.Uri;  
  8. class WeatherDataPoint {  
  9.     String city;  
  10.     int degrees;  
  11.     WeatherDataPoint(String c, int d) {  
  12.         city = c;  
  13.         degrees = d;  
  14.     }  
  15. }  
  16. public class WeatherDataProvider extends ContentProvider {  
  17.       
  18.     public static final Uri CONTENT_URI = Uri.parse("content://com.example.android.weatherlistwidget.provider");  
  19.       
  20.     public static class Columns {  
  21.         public static final String ID = "_id";  
  22.         public static final String CITY = "city";  
  23.         public static final String TEMPERATURE = "temperature";  
  24.     }  
  25.     private static final ArrayList<WeatherDataPoint> sData = new ArrayList<WeatherDataPoint>();  
  26.     /** 
  27.      * 在onCreate()时初始化数据。 
  28.      */  
  29.     @Override  
  30.     public boolean onCreate() {  
  31.         sData.add(new WeatherDataPoint("San Francisco", 13));  
  32.         sData.add(new WeatherDataPoint("New York", 1));  
  33.         sData.add(new WeatherDataPoint("Seattle", 7));  
  34.         sData.add(new WeatherDataPoint("Boston", 4));  
  35.         sData.add(new WeatherDataPoint("Miami", 22));  
  36.         sData.add(new WeatherDataPoint("Toronto", -10));  
  37.         sData.add(new WeatherDataPoint("Calgary", -13));  
  38.         sData.add(new WeatherDataPoint("Tokyo", 8));  
  39.         sData.add(new WeatherDataPoint("Kyoto", 11));  
  40.         sData.add(new WeatherDataPoint("London", -1));  
  41.         sData.add(new WeatherDataPoint("Nomanisan", 27));  
  42.         return true;  
  43.     }  
  44.     @Override  
  45.     public synchronized Cursor query(Uri uri, String[] projection, String selection,  
  46.             String[] selectionArgs, String sortOrder) {  
  47.         assert(uri.getPathSegments().isEmpty());  
  48.         /** 
  49.          * 创建MatrixCursor,并向MatrixCursor中添加数据,返回。 
  50.          */  
  51.         final MatrixCursor c = new MatrixCursor(new String[]{ Columns.ID, Columns.CITY, Columns.TEMPERATURE });  
  52.         for (int i = 0; i < sData.size(); ++i) {  
  53.             final WeatherDataPoint data = sData.get(i);  
  54.             c.addRow(new Object[]{ new Integer(i), data.city, new Integer(data.degrees) });  
  55.         }  
  56.           
  57.         return c;  
  58.     }  
  59.     @Override  
  60.     public String getType(Uri uri) {  
  61.         return "vnd.android.cursor.dir/vnd.weatherlistwidget.citytemperature";  
  62.     }  
  63.     @Override  
  64.     public Uri insert(Uri uri, ContentValues values) {  
  65.         return null;  
  66.     }  
  67.     @Override  
  68.     public int delete(Uri uri, String selection, String[] selectionArgs) {  
  69.         return 0;  
  70.     }  
  71.     @Override  
  72.     public synchronized int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {  
  73.         assert(uri.getPathSegments().size() == 1);  
  74.           
  75.         final int index = Integer.parseInt(uri.getPathSegments().get(0));  
  76.         assert(0 <= index && index < sData.size());  
  77.         final WeatherDataPoint data = sData.get(index);  
  78.         data.degrees = values.getAsInteger(Columns.TEMPERATURE);  
  79.         /** 
  80.          * 提醒ContentObserver数据改变。 
  81.          */  
  82.         getContext().getContentResolver().notifyChange(uri, null);  
  83.           
  84.         return 1;  
  85.     }  
  86. }  

 

    (5)AppWidget类,如下:

[java] view plaincopy
 
  1. package com.example.android.weatherlistwidget;  
  2. import java.util.Random;  
  3. import android.app.PendingIntent;  
  4. import android.appwidget.AppWidgetManager;  
  5. import android.appwidget.AppWidgetProvider;  
  6. import android.content.ComponentName;  
  7. import android.content.ContentResolver;  
  8. import android.content.ContentUris;  
  9. import android.content.ContentValues;  
  10. import android.content.Context;  
  11. import android.content.Intent;  
  12. import android.database.ContentObserver;  
  13. import android.database.Cursor;  
  14. import android.net.Uri;  
  15. import android.os.Handler;  
  16. import android.os.HandlerThread;  
  17. import android.widget.RemoteViews;  
  18. import android.widget.Toast;  
  19. class WeatherDataProviderObserver extends ContentObserver {  
  20.       
  21.     private AppWidgetManager mAppWidgetManager;  
  22.     private ComponentName mComponentName;  
  23.     WeatherDataProviderObserver(AppWidgetManager mgr, ComponentName cn, Handler h) {  
  24.         super(h);  
  25.         mAppWidgetManager = mgr;  
  26.         mComponentName = cn;  
  27.     }  
  28.     /** 
  29.      * ContentProvider的内容改变后会通过AppWidget组件中的ListView重新渲染数据。这里会调用RemoteViewService中RemoteViewsFactory的onDataSetChanged方法。 
  30.      */  
  31.     @Override  
  32.     public void onChange(boolean selfChange) {  
  33.         mAppWidgetManager.notifyAppWidgetViewDataChanged(mAppWidgetManager.getAppWidgetIds(mComponentName), R.id.weather_list);  
  34.     }  
  35.       
  36. }  
  37. public class WeatherWidgetProvider extends AppWidgetProvider {  
  38.     public static String CLICK_ACTION = "com.example.android.weatherlistwidget.CLICK";  
  39.     public static String REFRESH_ACTION = "com.example.android.weatherlistwidget.REFRESH";  
  40.     public static String EXTRA_CITY_ID = "com.example.android.weatherlistwidget.city";  
  41.     private static HandlerThread sWorkerThread;  
  42.     private static Handler sWorkerQueue;  
  43.     private static WeatherDataProviderObserver sDataObserver;  
  44.     public WeatherWidgetProvider() {  
  45.         sWorkerThread = new HandlerThread("WeatherWidgetProvider-worker");  
  46.         sWorkerThread.start();  
  47.         sWorkerQueue = new Handler(sWorkerThread.getLooper());  
  48.     }  
  49.     @Override  
  50.     public void onEnabled(Context context) {  
  51.         final ContentResolver r = context.getContentResolver();  
  52.           
  53.         /** 
  54.          * 在发出AppWidget的Enable广播后向WeatherDataProvider注册WeatherDataProviderObserver(ContentObserver)。 
  55.          */  
  56.         if (sDataObserver == null) {  
  57.             final AppWidgetManager mgr = AppWidgetManager.getInstance(context);  
  58.             final ComponentName cn = new ComponentName(context, WeatherWidgetProvider.class);  
  59.             sDataObserver = new WeatherDataProviderObserver(mgr, cn, sWorkerQueue);  
  60.             r.registerContentObserver(WeatherDataProvider.CONTENT_URI, true, sDataObserver);  
  61.         }  
  62.     }  
  63.     @Override  
  64.     public void onReceive(Context ctx, Intent intent) {  
  65.         final String action = intent.getAction();  
  66.           
  67.         if (action.equals(REFRESH_ACTION)) {  
  68.             final Context context = ctx;  
  69.             sWorkerQueue.removeMessages(0);  
  70.             sWorkerQueue.post(new Runnable() {  
  71.                 /** 
  72.                  * 单击刷新按钮后会更新数据。 
  73.                  */  
  74.                 @Override  
  75.                 public void run() {  
  76.                     final ContentResolver r = context.getContentResolver();  
  77.                     final Cursor c = r.query(WeatherDataProvider.CONTENT_URI, null, null, null,   
  78.                             null);  
  79.                     final int count = c.getCount();  
  80.                     final int maxDegrees = 96;  
  81.                     r.unregisterContentObserver(sDataObserver);  
  82.                     for (int i = 0; i < count; ++i) {  
  83.                         final Uri uri = ContentUris.withAppendedId(WeatherDataProvider.CONTENT_URI, i);  
  84.                         final ContentValues values = new ContentValues();  
  85.                         values.put(WeatherDataProvider.Columns.TEMPERATURE,  
  86.                                 new Random().nextInt(maxDegrees));  
  87.                         r.update(uri, values, null, null);  
  88.                     }  
  89.                     r.registerContentObserver(WeatherDataProvider.CONTENT_URI, true, sDataObserver);  
  90.                     final AppWidgetManager mgr = AppWidgetManager.getInstance(context);  
  91.                     final ComponentName cn = new ComponentName(context, WeatherWidgetProvider.class);  
  92.                       
  93.                     /** 
  94.                      * 这句话会调用RemoteViewSerivce中RemoteViewsFactory的onDataSetChanged()方法。 
  95.                      */  
  96.                     mgr.notifyAppWidgetViewDataChanged(mgr.getAppWidgetIds(cn), R.id.weather_list);  
  97.                 }  
  98.             });  
  99.         } else if (action.equals(CLICK_ACTION)) {  
  100.             /** 
  101.              * 单击ListView中的某一项会显示一个Toast提示。 
  102.              */  
  103.             final String city = intent.getStringExtra(EXTRA_CITY_ID);  
  104.             final String formatStr = ctx.getResources().getString(R.string.toast_format_string);  
  105.             Toast.makeText(ctx, String.format(formatStr, city), Toast.LENGTH_SHORT).show();  
  106.         }  
  107.         super.onReceive(ctx, intent);  
  108.     }  
  109.     @Override  
  110.     public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {  
  111.         for (int i = 0; i < appWidgetIds.length; ++i) {  
  112.               
  113.             /** 
  114.              * 创建请求Service的Intent对象。 
  115.              */  
  116.             final Intent intent = new Intent(context, WeatherWidgetService.class);  
  117.             intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);  
  118.             intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));  
  119.               
  120.             /** 
  121.              * 创建RemoteViews对象,在其中的布局中存在一个ListView组件widget_layout。 
  122.              */  
  123.             final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);  
  124.               
  125.             /** 
  126.              * 组件ListView组件weather_list设置RemoteViewService渲染ListView. 
  127.              */  
  128.             rv.setRemoteAdapter(appWidgetIds[i], R.id.weather_list, intent);  
  129.               
  130.             /** 
  131.              * 如果weather_list为空则显示empty_view。 
  132.              */  
  133.             rv.setEmptyView(R.id.weather_list, R.id.empty_view);  
  134.               
  135.             /** 
  136.              * 点击ListView某一项时会显示一个Toast提示。 
  137.              */  
  138.             final Intent onClickIntent = new Intent(context, WeatherWidgetProvider.class);  
  139.             onClickIntent.setAction(WeatherWidgetProvider.CLICK_ACTION);  
  140.             onClickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);  
  141.             onClickIntent.setData(Uri.parse(onClickIntent.toUri(Intent.URI_INTENT_SCHEME)));  
  142.             final PendingIntent onClickPendingIntent = PendingIntent.getBroadcast(context, 0,  
  143.                     onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT);  
  144.             rv.setPendingIntentTemplate(R.id.weather_list, onClickPendingIntent);  
  145.             /** 
  146.              * 点击刷新按钮时刷新ListView数据。 
  147.              */  
  148.             final Intent refreshIntent = new Intent(context, WeatherWidgetProvider.class);  
  149.             refreshIntent.setAction(WeatherWidgetProvider.REFRESH_ACTION);  
  150.             final PendingIntent refreshPendingIntent = PendingIntent.getBroadcast(context, 0,  
  151.                     refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT);  
  152.             rv.setOnClickPendingIntent(R.id.refresh, refreshPendingIntent);  
  153.             /** 
  154.              * 更新AppWidget组件。 
  155.              */  
  156.             appWidgetManager.updateAppWidget(appWidgetIds[i], rv);  
  157.         }  
  158.           
  159.         super.onUpdate(context, appWidgetManager, appWidgetIds);  
  160.     }  
  161. }  

 

 

    (6)RemoteViewsService渲染器,如下:

[java] view plaincopy
 
  1. package com.example.android.weatherlistwidget;  
  2. import android.content.Context;  
  3. import android.content.Intent;  
  4. import android.database.Cursor;  
  5. import android.os.Bundle;  
  6. import android.widget.RemoteViews;  
  7. import android.widget.RemoteViewsService;  
  8. public class WeatherWidgetService extends RemoteViewsService {  
  9.     @Override  
  10.     public RemoteViewsFactory onGetViewFactory(Intent intent) {  
  11.         return new StackRemoteViewsFactory(this.getApplicationContext(), intent);  
  12.     }  
  13. }  
  14. /** 
  15.  * 这个Factory就类拟一个Adapter,可以为AppWidget中的集合组件设置数据。 
  16.  */  
  17. class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {  
  18.     private Context mContext;  
  19.     private Cursor mCursor;  
  20.     public StackRemoteViewsFactory(Context context, Intent intent) {  
  21.         mContext = context;  
  22.     }  
  23.     public void onCreate() {  
  24.           
  25.     }  
  26.     public void onDestroy() {  
  27.         if (mCursor != null) {  
  28.             mCursor.close();  
  29.         }  
  30.     }  
  31.     /** 
  32.      * 获取Cursor中的数据个数。 
  33.      */  
  34.     public int getCount() {  
  35.         return mCursor.getCount();  
  36.     }  
  37.     /** 
  38.      * 获取ListView中的每一个条目View. 
  39.      */  
  40.     public RemoteViews getViewAt(int position) {  
  41.         String city = "Unknown City";  
  42.         int temp = 0;  
  43.         if (mCursor.moveToPosition(position)) {  
  44.             final int cityColIndex = mCursor.getColumnIndex(WeatherDataProvider.Columns.CITY);  
  45.             final int tempColIndex = mCursor.getColumnIndex(  
  46.                     WeatherDataProvider.Columns.TEMPERATURE);  
  47.             city = mCursor.getString(cityColIndex);  
  48.             temp = mCursor.getInt(tempColIndex);  
  49.         }  
  50.         final String formatStr = mContext.getResources().getString(R.string.item_format_string);  
  51.         final int itemId = (position % 2 == 0 ? R.layout.light_widget_item  
  52.                 : R.layout.dark_widget_item);  
  53.         RemoteViews rv = new RemoteViews(mContext.getPackageName(), itemId);  
  54.         rv.setTextViewText(R.id.widget_item, String.format(formatStr, temp, city));  
  55.         final Intent fillInIntent = new Intent();  
  56.         final Bundle extras = new Bundle();  
  57.         extras.putString(WeatherWidgetProvider.EXTRA_CITY_ID, city);  
  58.         fillInIntent.putExtras(extras);  
  59.         rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);  
  60.         return rv;  
  61.     }  
  62.     public RemoteViews getLoadingView() {  
  63.         return null;  
  64.     }  
  65.     public int getViewTypeCount() {  
  66.         return 2;  
  67.     }  
  68.     public long getItemId(int position) {  
  69.         return position;  
  70.     }  
  71.     public boolean hasStableIds() {  
  72.         return true;  
  73.     }  
  74.     /** 
  75.      * 在AppWidget被第一次加载到屏幕时会自动调用此方法或者调用AppWidgetManager.notifyAppWidgetViewDataChanged()方法也会调用此方法。 
  76.      */  
  77.     public void onDataSetChanged() {  
  78.         if (mCursor != null) {  
  79.             mCursor.close();  
  80.         }  
  81.           
  82.         mCursor = mContext.getContentResolver().query(WeatherDataProvider.CONTENT_URI, null, null, null, null);  
  83.     }  
  84.       
  85. }  

 原文:http://blog.csdn.net/mayingcai1987/article/details/6264213

 

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