大神skywang-http://www.cnblogs.com/skywang12345/p/3264991.html


Android 之窗口小部件高级篇--App Widget 之 RemoteViews


在之前的一篇博文(Android 之窗口小部件详解--App Widget)中,已经介绍了App Widget的基本用法和简单实例。这篇主要讲解 App Widget 的高级内容,即通过 RemoteViews 去管理Widget的中GridView、ListView、StackView等内容。在学习本篇之前,建议读者先掌握 App Widget 的基本知识。

1 RemoteViews等相关类的介绍

下面先简单介绍RemoteViews、RemoteViewsService、RemoteViewsFactory。

1.1 RemoteViews

顾名思义,它是一个远程视图。App Widget中的视图,都是通过RemoteViews表现的。
在RemoteViews的构造函数中,通过传入layout文件的id来获取 “layout文件对应的视图(RemoteViews)”;然后,调用RemoteViews中的方法能对layout中的组件进行设置(例如,可以调用setTextViewText()来设置TextView组件的文本,可以调用setOnClickPendingIntent() 来设置Button的点击响应事件)。

因此,我们可以将 “RemoteViews 看作是 layout文件中所包含的全部视图的集合”。

1.2 RemoteViewsService

RemoteViewsService,是管理RemoteViews的服务。
一般,当App Widget 中包含“GridView、ListView、StackView等”集合视图时,才需要使用RemoteViewsService来进行更新、管理。(集合视图是指GridView、ListView、StackView等包含子元素的视图)
RemoteViewsService更新“集合视图”的一般步骤是:
(01) 通过setRemoteAdapter来设置 “RemoteViews对应RemoteViewsService”。
(02) 之后在RemoteViewsService中,实现RemoteViewsFactory接口。然后,在RemoteViewsFactory接口中对“集合视图”的各个子项进行设置(“集合视图”的各个子项:例如,GridView的每一个格子都是一个子项;ListView中的每一列也是一个子项)。

因此,我们可以将 “RemoteViewsService 看作是 管理layout中集合视图的服务”。

1.3 RemoteViewsFactory

通过RemoteViewsService中的介绍,我们可以了解“RemoteViewsService是通过RemoteViewsFactory来具体管理layout中集合视图的”,即“RemoteViewsFactory管理集合视图的实施者”。
RemoteViewsFactory是RemoteViewsService中的一个接口。RemoteViewsFactory提供了一系列的方法管理“集合视图”中的每一项。例如:
(01)RemoteViews getViewAt(int position)
通过getViewAt()来获取“集合视图”中的第position项的视图,视图是以RemoteViews的对象返回的。
(02)int getCount()
通过getCount()来获取“集合视图”中所有子项的总数。

因此,我们可以将 “RemoteViewsFactory 看作是 layout中集合视图管理的具体实施者”。

2 实例介绍

实现一个App Widget,App Widget可缩放,且包含“3个组成部分”。
第1部分:是一个TextView文本,标题内容是“Sky Wang”。
第2部分:是一个Button按钮。点击按钮,会弹出一个Toast提示框,提示响应了Button点击事件。
第3部分:是一个GridView视图。GridView的每一个格子包含“图片”和“文本”两部分数据。点击GridView中的每一个格子,会弹出响应的提示语。

manifest代码如下

<manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.skywang.test"    android:versionCode="1"    android:versionName="1.0" >    <uses-sdk        android:minSdkVersion="14"        android:targetSdkVersion="17" />    <application        android:allowBackup="true"        android:icon="@drawable/ic_launcher"        android:label="@string/app_name"        android:theme="@style/AppTheme" >                <receiver android:name=".GridWidgetProvider">            <intent-filter>                                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />                                <!-- GridWidgetProvider接收点击gridview的响应事件 -->                <action android:name="com.skywang.test.COLLECTION_VIEW_ACTION" />                <!-- GridWidgetProvider接收点击bt_refresh的响应事件 -->                <action android:name="com.skywang.test.BT_REFRESH_ACTION" />            </intent-filter>            <meta-data android:name="android.appwidget.provider"                android:resource="@xml/widget_provider"/>        </receiver>                        <service               android:name=".GridWidgetService"              android:permission="android.permission.BIND_REMOTEVIEWS" />                  </application></manifest>

说明:
(01)GridWidgetProvider.javaAppWidgetProvider的继承类,而且AppWidgetProvider的配置文件是widget_provider.xml
(02)GridWidgetProvider.java除了响应 App Widget 的更新事件(android.appwidget.action.APPWIDGET_UPDATE)之外;
也会“响应App Widget包含的GridView的点击事件(com.skywang.test.COLLECTION_VIEW_ACTION) ”和“App Widget包含的按钮的点击事件(com.skywang.test.BT_REFRESH_ACTION) ”。
(03)GridWidgetService.javaRemoteViewsService的继承类


widget_provider.xml代码如下
widget_provider.xml是AppWidgetProvider对应配置文件

<?xml version="1.0" encoding="utf-8"?><appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"    android:minWidth="180dp"    android:minHeight="180dp"    android:previewImage="@drawable/preview"    android:initialLayout="@layout/widget_layout"    android:resizeMode="horizontal|vertical"    android:widgetCategory="home_screen">     </appwidget-provider>

说明:
(01)android:minWidth="180dp" 表明 "Widget支持的最小宽度是3格"
(02)android:minHeight="180dp" 表明 "Widget支持的最小高度是3格"
(03)android:initialLayout="@layout/widget_layout"表明 "Widget对应的布局文件是widget_layout.xml"
(04)android:previewImage="@drawable/preview" 表明 "Widget对应的预览图片是preview.png"
(05)android:resizeMode="horizontal|vertical" 表明 "Widget支持水平和竖直伸缩"
(06)android:widgetCategory="home_screen" 表明 "Widget只能添加到桌面上,而不能添加到锁屏界面上"

widget_layout.xml代码如下
widget_layout.xml是App Widget的布局文件

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical" >        <RelativeLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginLeft="8dip"        android:layout_marginRight="8dip"        >                <TextView            android:id="@+id/tv_head"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_alignParentLeft="true"            android:layout_alignParentTop="true"            android:gravity="left|center_vertical"            android:textSize="24sp"            android:text="SkyWang" />                <Button            android:id="@+id/bt_refresh"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_alignParentTop="true"            android:layout_alignParentRight="true"            android:gravity="right|center_vertical"            android:textSize="18sp"            android:text="Refresh" />            </RelativeLayout>                        <GridView        android:id="@+id/gridview"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:numColumns="auto_fit"        android:verticalSpacing="4dip"        android:horizontalSpacing="4dip"        android:columnWidth="80dip"        android:gravity="center" />        </LinearLayout>


GridWidgetProvider.java代码如下
GridWidgetProvider.java是AppWidgetProvider的继承类

package com.skywang.test;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.util.Log;import android.widget.RemoteViews;import android.widget.Toast;import com.skywang.test.R;/** * @desc App Widget高级功能测试程序 * @author skywang * */public class GridWidgetProvider extends AppWidgetProvider {        private static final String TAG = "SKYWANG";    public static final String BT_REFRESH_ACTION = "com.skywang.test.BT_REFRESH_ACTION";    public static final String COLLECTION_VIEW_ACTION = "com.skywang.test.COLLECTION_VIEW_ACTION";    public static final String COLLECTION_VIEW_EXTRA = "com.skywang.test.COLLECTION_VIEW_EXTRA";        @Override      public void onUpdate(Context context, AppWidgetManager appWidgetManager,              int[] appWidgetIds) {          Log.d(TAG, "GridWidgetProvider onUpdate");        for (int appWidgetId:appWidgetIds) {            // 获取AppWidget对应的视图            RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);                        // 设置响应 “按钮(bt_refresh)” 的intent            Intent btIntent = new Intent().setAction(BT_REFRESH_ACTION);            PendingIntent btPendingIntent = PendingIntent.getBroadcast(context, 0, btIntent, PendingIntent.FLAG_UPDATE_CURRENT);            rv.setOnClickPendingIntent(R.id.bt_refresh, btPendingIntent);                                    // 设置 “GridView(gridview)” 的adapter。            // (01) intent: 对应启动 GridWidgetService(RemoteViewsService) 的intent              // (02) setRemoteAdapter: 设置 gridview的适配器            //    通过setRemoteAdapter将gridview和GridWidgetService关联起来,            //    以达到通过 GridWidgetService 更新 gridview 的目的            Intent serviceIntent = new Intent(context, GridWidgetService.class);                    rv.setRemoteAdapter(R.id.gridview, serviceIntent);                                                // 设置响应 “GridView(gridview)” 的intent模板                        // 说明:“集合控件(如GridView、ListView、StackView等)”中包含很多子元素,如GridView包含很多格子。            //     它们不能像普通的按钮一样通过 setOnClickPendingIntent 设置点击事件,必须先通过两步。            //        (01) 通过 setPendingIntentTemplate 设置 “intent模板”,这是比不可少的!            //        (02) 然后在处理该“集合控件”的RemoteViewsFactory类的getViewAt()接口中 通过 setOnClickFillInIntent 设置“集合控件的某一项的数据”            Intent gridIntent = new Intent();            gridIntent.setAction(COLLECTION_VIEW_ACTION);            gridIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);            PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, gridIntent, PendingIntent.FLAG_UPDATE_CURRENT);            // 设置intent模板            rv.setPendingIntentTemplate(R.id.gridview, pendingIntent);            // 调用集合管理器对集合进行更新            appWidgetManager.updateAppWidget(appWidgetId, rv);        }        super.onUpdate(context, appWidgetManager, appWidgetIds);    }        @Override    public void onReceive(Context context, Intent intent) {        String action = intent.getAction();                AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);        Log.d(TAG, "GridWidgetProvider onReceive : "+intent.getAction());        if (action.equals(COLLECTION_VIEW_ACTION)) {            // 接受“gridview”的点击事件的广播            int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,                AppWidgetManager.INVALID_APPWIDGET_ID);            int viewIndex = intent.getIntExtra(COLLECTION_VIEW_EXTRA, 0);            Toast.makeText(context, "Touched view " + viewIndex, Toast.LENGTH_SHORT).show();        } else if (action.equals(BT_REFRESH_ACTION)) {            // 接受“bt_refresh”的点击事件的广播            Toast.makeText(context, "Click Button", Toast.LENGTH_SHORT).show();        }        super.onReceive(context, intent);    }}

说明:
(01)RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
通过上面的语句,获取widget_layout.xml对应的 RemoteViews;进而通过RemoteViews对 widget_layout.xml中的各个元素进行管理。
(02)rv.setOnClickPendingIntent(R.id.bt_refresh, btPendingIntent);
通过上面的语句,设置点击“按钮(bt_refresh)”时会触发的Intent。从而对按钮点击事件进行处理。
(03)rv.setRemoteAdapter(R.id.gridview, serviceIntent);
通过上面的语句,设置 “GridView(gridview)” 的远程适配器,serviceIntent是 GridWidgetService 的Intent。
从而在通过GridWidgetService对gridview进行管理。
(04)PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, gridIntent, PendingIntent.FLAG_UPDATE_CURRENT);
通过上面的语句,设置响应 “GridView(gridview)” 点击事件的intent模板。关于“Intent模板”,后面在"关于'GridView'的点击事件"会详细说明。
(05)if (action.equals(COLLECTION_VIEW_ACTION)) ...
通过上面的语句,响应“GridView”的点击事件。
(06)if (action.equals(BT_REFRESH_ACTION)) ...
通过上面的语句,响应“按钮(bt_refresh)”的点击事件。

关于 “GridView”的点击事件。这里详细说明以下!

像GridView这样的集合控件,不能单单像按钮一样,通过setOnClickPendingIntent() 来设置它的点击事件的Intent;而要通过以下两步来进行。
第一,设置GridView的点击响应事件的“Intent模板”。
这是通过setPendingIntentTemplate()来进行设置的。
这样做的目的有两个:首先,设置Intent模板。因为GridView有许多子项,它们这些子项都统一的要响应父亲的Intent模板。其次,传递附件参数(例如,App Widget的ID),因为App Widget可以设置许多widget,每一个Widget的ID都不同,而且它们显示的内容可能不同(例如,不同大小的Widget显示不同大小的文字)。
第二,设置GridView子项的点击响应事件的Intent。
设置通过setOnClickFillInIntent()来进行设置的。
这样做的首要目的,是设置 GridView的子项所包含的信息(例如,点击的GridView子项的索引值)。
通过这两步的设置之后,点击GridView中具体每一项的所产生的事件就是“第一”和“第二”步中Intent的合集(组合)。也就是说,点击“GridView中某一个子项”所产生的Intent,同时包含了“通过setPendingIntentTemplate传递的Intent数据”和“通过setOnClickFillInIntent传递的Intent数据”理解这一点对理解这个RemoteView的原理至关重要!!!


GridWidgetService.java的代码如下:

package com.skywang.test;import android.app.PendingIntent;import android.appwidget.AppWidgetManager;import android.content.Context;import android.content.Intent;import android.widget.RemoteViews;import android.widget.RemoteViewsService;import android.util.Log;import java.util.List;import java.util.ArrayList;import java.util.HashMap;public class GridWidgetService extends RemoteViewsService{    private static final String TAG = "SKYWANG";    @Override    public RemoteViewsService.RemoteViewsFactory onGetViewFactory(Intent intent) {        Log.d(TAG, "GridWidgetService");        return new GridRemoteViewsFactory(this, intent);    }        private class GridRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {        private Context mContext;        private int mAppWidgetId;                private String IMAGE_ITEM = "imgage_item";        private String TEXT_ITEM = "text_item";        private ArrayList<HashMap<String, Object>> data ;                private String[] arrText = new String[]{                 "Picture 1", "Picture 2", "Picture 3",                 "Picture 4", "Picture 5", "Picture 6",                "Picture 7", "Picture 8", "Picture 9"                };        private int[] arrImages=new int[]{                R.drawable.p1, R.drawable.p2, R.drawable.p3,                 R.drawable.p4, R.drawable.p5, R.drawable.p6,                 R.drawable.p7, R.drawable.p8, R.drawable.p9                };                /**         * 构造GridRemoteViewsFactory         * @author skywang         */        public GridRemoteViewsFactory(Context context, Intent intent) {            mContext = context;            mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,                    AppWidgetManager.INVALID_APPWIDGET_ID);            Log.d(TAG, "GridRemoteViewsFactory mAppWidgetId:"+mAppWidgetId);        }                @Override        public RemoteViews getViewAt(int position) {            HashMap<String, Object> map;             Log.d(TAG, "GridRemoteViewsFactory getViewAt:"+position);            // 获取 grid_view_item.xml 对应的RemoteViews            RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.grid_view_item);                        // 设置 第position位的“视图”的数据            map = (HashMap<String, Object>) data.get(position);            rv.setImageViewResource(R.id.itemImage, ((Integer)map.get(IMAGE_ITEM)).intValue());            rv.setTextViewText(R.id.itemText, (String)map.get(TEXT_ITEM));            // 设置 第position位的“视图”对应的响应事件            Intent fillInIntent = new Intent();            fillInIntent.putExtra(GridWidgetProvider.COLLECTION_VIEW_EXTRA, position);            rv.setOnClickFillInIntent(R.id.itemLayout, fillInIntent);                        return rv;        }                /**         * 初始化GridView的数据         * @author skywang         */        private void initGridViewData() {            data = new ArrayList<HashMap<String, Object>>();                        for (int i=0; i<9; i++) {                HashMap<String, Object> map = new HashMap<String, Object>();                 map.put(IMAGE_ITEM, arrImages[i]);                map.put(TEXT_ITEM, arrText[i]);                data.add(map);            }        }                @Override        public void onCreate() {            Log.d(TAG, "onCreate");            // 初始化“集合视图”中的数据            initGridViewData();        }                @Override        public int getCount() {            // 返回“集合视图”中的数据的总数            return data.size();        }                @Override        public long getItemId(int position) {            // 返回当前项在“集合视图”中的位置            return position;        }        @Override        public RemoteViews getLoadingView() {            return null;        }                @Override        public int getViewTypeCount() {            // 只有一类 GridView            return 1;        }        @Override        public boolean hasStableIds() {            return true;        }                @Override        public void onDataSetChanged() {                    }                @Override        public void onDestroy() {            data.clear();        }    }}

说明:
(01)public RemoteViewsService.RemoteViewsFactory onGetViewFactory(Intent intent) ...
通过上面的语句,返回一个RemoteViewsFactory对象。
RemoteViewsService是一个服务,通过之前对RemoteViewsService的介绍。我们知道,它 管理layout中集合视图的服务。
RemoteViewsService管理layout中集合视图的服务,是通过 RemoteViewsFactory 实现的;那么它是如何实现的呢?
RemoteViewsService是“通过 onGetViewFactory() 接口返回一个 RemoteViewsFactory 对象” 来实现的。
(02)public void onCreate() ...
在onCreate()中进行RemoteViewsFactory 的初始化工作
(03)public int getCount() ...
通过getCount()返回“集合视图”中的数据项的总数。
(04)public RemoteViews getViewAt(int position) ...
通过上面的语句,返回一个“集合视图”中具体每一项的视图。
getViewAt()是非常重要的函数!我们对"集合视图"中每一项的初始化都是在getViewAt()中进行设置的


grid_view_item.xml的代码如下:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/itemLayout"    android:layout_height="match_parent"     android:layout_width="match_parent">        <ImageView        android:id="@+id/itemImage"        android:layout_width="80dip"        android:layout_height="60dip"        android:layout_centerHorizontal="true"        android:scaleType="fitXY" />        <TextView         android:id="@+id/itemText"         android:layout_width="wrap_content"        android:layout_height="wrap_content"         android:layout_below="@+id/itemImage"        android:layout_centerHorizontal="true"         android:text="TextView01"/>    </RelativeLayout>


点击下载:源代码

更多相关文章

  1. android 动态创建控件并设置布局
  2. android 中如何在androidmanifest.xml设置权限请求
  3. Android(安卓)SDK访问权限大全
  4. android persist属性使用
  5. radiobutton实现底部导航
  6. Kotlin Android(安卓)Extensions的使用
  7. android:Adapter中无法设置textview字体颜色(解决)
  8. GitHub上受欢迎的Android(安卓)UI Library
  9. setBackground、setBackgroundDrawable、setBackgroundResource

随机推荐

  1. android 修改头像后并上传到阿里云
  2. eclipse android main.xml error parsing
  3. My Android成长之路(二)——【JSON】
  4. Android上实现Push
  5. Android 移植到C#
  6. 《转》Android 今日头条屏幕适配方案终极
  7. Android设备一对多录屏直播--(UDP组播连
  8. Android Launcher分析和修改2——Icon修
  9. Android中 将布局文件/View显示至手机屏
  10. 超经典的Android开源项目