zoukankan      html  css  js  c++  java
  • Android 之窗口小部件高级篇--App Widget 之 RemoteViews

      在之前的一篇博文( Android 之窗口小部件详解--App Widge t)中,已经介绍了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.java 是 AppWidgetProvider的继承类 ,而且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.java 是 RemoteViewsService的继承类 。

    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 之窗口小部件详解--App Widget 
    2. sky wang博客索引

    实例效果图:

  • 相关阅读:
    CSUFT 1002 Robot Navigation
    CSUFT 1003 All Your Base
    Uva 1599 最佳路径
    Uva 10129 单词
    欧拉回路
    Uva 10305 给任务排序
    uva 816 Abbott的复仇
    Uva 1103 古代象形文字
    Uva 10118 免费糖果
    Uva 725 除法
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/4422415.html
Copyright © 2011-2022 走看看