导读

本篇文章将介绍”集合视图”,App Widget 复杂布局的实现

  • App Widget 小部件系列其他文章链接

App Widgets 详解一 简单使用

App Widgets 详解二 Configuration Activity

App Widgets 详解三 Activity中添加App Widgets

App Widgets 详解四 RemoteViews、RemoteViewsService和RemoteViewsFactory

RemoteViews、RemoteViewsService和RemoteViewsFactory 简介

RemoteViews 构造函数

远程视图,App Widget中的视图,都是通过RemoteViews实现.

在RemoteViews的构造函数中,通过传入R.layout.XX(AppWidgets 的XML布局文件),拿到该Layout中的所有View视图;

再通过RemoteViews.setTextView()、RemoteViews.setOnClickPendingIntent()等方法设置对应组件的响应事件

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

RemoteViews 官方文档

==注意==

由于Widget的布局需要RemoteViews支持,因此不能随便定义或自定义view(可尝试是重写remoteViews)**

支持的布局:

  • FrameLayout
  • LinearLayout
  • RelativeLayout
  • GridLayout

支持的控件:

  • AnalogClock
  • Button
  • Chronometer
  • ImageButton
  • ImageView
  • ProgressBar
  • TextView
  • ViewFlipper
  • ListView
  • GridView
  • StackView
  • AdapterViewFlipper

其中 ViewFlipper,ListView,GridView,StackView,AdapterViewFlipper 等包含子元素的视图,属于”集合视图”

RemoteViewsService 类

在AppWidgetProvider类中,RemoteViewsService作为一个接口适配器Service,用于实现RemoteViews对象中的集合视图

RemoteViewsService更新”集合视图”的一般步骤是:

  1. 通过RemoteViews.setRemoteAdapter(R.id.ListView_ID,Service 的 intent)来设置 “RemoteViews对应RemoteViewsService”

  2. 在RemoteViewsService中,实现RemoteViews对应RemoteViewsService.RemoteViewsFactory接口.

  3. 在RemoteViewsFactory接口中对”集合视图”的各个需要实现的方法进行设置

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

RemoteViewsService 官方文档

RemoteViewsFactory 接口

RemoteViewsService.RemoteViewsFactory是RemoteViewsService的子类,用于管理RemoteViews远程集合视图(GridView、ListView、StackView、AdapterViewFlipper等)

该接口类似ListView 的 BaseAdapter 用于将View与数据绑定并显示,其中比较重要的两个方法是onCreate()和getViewAt(int position)

  • onCreat() : 用于初始化数据,首次创建Factory时被调用
  • getViewAt(int position) : 获取”集合视图”中的第position项的视图,返回RemoteViews()

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

RemoteViewsFactory 官方文档

注意:我们不能在Service 或单例中持久化数据.因此,我们不应该在RemoteViewsService中存储任何数据(除非它是静态的).如果希望AppWidget的数据持续存在,最好的方法是使用ContentProvider

“集合视图” 开发说明(ListView为例):

一、在清单文件配置service节点和receiver节点


<!--MyRemoteService--><service            android:name=".remote.MyRemoteService"android:exported="false"android:permission="android.permission.BIND_REMOTEVIEWS"></service><!--MyRemoteAppWidget--><receiver android:name=".remote.MyRemoteAppWidget"><intent-filter><!--指定AppWidgetProvider接受系统的APPWIDGET_UPDATE广播--><action android:name="android.appwidget.action.APPWIDGET_UPDATE"/></intent-filter><!--指定Meta_data名称,使用android.appwidgetb必须确定AppWidgetProviderInfo描述符的数据--><!--指定AppWidgetProviderInfo资源XML文件--><meta-data                android:name="android.appwidget.provider"android:resource="@xml/my_remote_widget_info"/></receiver>

二、创建AppWidgetProviderInfo XML文件

该XML文件定义 App Widget 的基本属性,在res/xml/目录下创建appwidger-provider 标签的XML文件


<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"android:initialKeyguardLayout="@layout/my_remote_widget"android:initialLayout="@layout/my_remote_widget"android:minHeight="50dp"android:minWidth="50dp"android:previewImage="@mipmap/ic_launcher"android:resizeMode="horizontal|vertical"android:updatePeriodMillis="86400000"android:widgetCategory="home_screen|keyguard">
</appwidget-provider>

三、定义 AppWidgetProvider 类


public class MyRemoteAppWidget extends AppWidgetProvider {static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,int appWidgetId) {// 获取Widget的组件名ComponentName thisWidget = new ComponentName(context,MyRemoteAppWidget.class);// 创建一个RemoteViewRemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.my_remote_widget);// 把这个Widget绑定到RemoteViewsServiceIntent intent = new Intent(context, MyRemoteService.class);// When intents are compared, the extras are ignored, so we need to embed the extras// into the data so that the extras will not be ignored.intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));//设置适配器remoteViews.setRemoteAdapter(R.id.widget_list, intent);//TODO 设置当显示的widget_list为空显示的View remoteViews.setEmptyView();// 设置点击列表触发事件Intent clickIntent = new Intent(context, MyRemoteAppWidget.class);// Set the action for the intent.// When the user touches a particular view, it will have the effect of// broadcasting TOAST_ACTION.// 设置Action,方便在onReceive中区别点击事件clickIntent.setAction("clickAction");clickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);clickIntent.setData(Uri.parse(clickIntent.toUri(Intent.URI_INTENT_SCHEME)));PendingIntent pendingIntentTemplate = PendingIntent.getBroadcast(context, 0, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);//使用"集合视图",如果直接setOnClickPendingIntent是不可行的,//建议setPendingIntentTemplate和FillInIntent结合使用//FillInIntent用于区分单个点击事件remoteViews.setPendingIntentTemplate(R.id.widget_list,pendingIntentTemplate);// 刷新按钮final Intent refreshIntent = new Intent(context,MyRemoteAppWidget.class);refreshIntent.setAction("refresh");final PendingIntent refreshPendingIntent = PendingIntent.getBroadcast(context, 0, refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT);remoteViews.setOnClickPendingIntent(R.id.button_refresh,refreshPendingIntent);// 更新WidgetappWidgetManager.updateAppWidget(appWidgetId, remoteViews);}@Overridepublic void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {// There may be multiple widgets active, so update all of themfor (int appWidgetId : appWidgetIds) {updateAppWidget(context, appWidgetManager, appWidgetId);}}@Overridepublic void onEnabled(Context context) {// Enter relevant functionality for when the first widget is createdToast.makeText(context, "用户将widget添加桌面了",Toast.LENGTH_SHORT).show();}@Overridepublic void onDisabled(Context context) {// Enter relevant functionality for when the last widget is disabled}@Overridepublic void onDeleted(Context context, int[] appWidgetIds) {Toast.makeText(context, "用户将widget从桌面移除了",Toast.LENGTH_SHORT).show();super.onDeleted(context, appWidgetIds);}/*** 接受Intent**@param context*@param intent*/@Overridepublic void onReceive(Context context, Intent intent) {super.onReceive(context, intent);String action = intent.getAction();if (action.equals("refresh")) {int i = 0;// 刷新Widgetfinal AppWidgetManager mgr = AppWidgetManager.getInstance(context);final ComponentName cn = new ComponentName(context,MyRemoteAppWidget.class);MyRemoteViewsFactory.mList.add("音乐" + i);i=i+1;// 这句话会调用RemoteViewSerivce中RemoteViewsFactory的onDataSetChanged()方法。mgr.notifyAppWidgetViewDataChanged(mgr.getAppWidgetIds(cn),R.id.widget_list);} else if (action.equals("clickAction")) {// 单击Wdiget中ListView的某一项会显示一个Toast提示。Toast.makeText(context, intent.getStringExtra("content"),Toast.LENGTH_SHORT).show();}}
}

==注意==

  1. RemoteViews.setEmptyView() 设置空视图必须是集合视图的兄弟节点,空视图表示空状态 (没数据时设置空视图??)
  2. 当我们使用集合视图,如LIstView,除了创建AppWidgets的XML布局,还需要创建list item 的XML布局

四、配置 AppWidgets 和 List_ltem 的XML布局文件

my_remote_widget.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@android:color/white"android:orientation="vertical" ><Button        android:id="@+id/button_refresh"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_marginTop="2dp"android:text="添加" /><ListView        android:divider="#000"android:id="@+id/widget_list"android:layout_width="match_parent"android:layout_height="wrap_content"android:cacheColorHint="#00000000"android:scrollbars="none" /><!-- 此处的ListView 可以换成StackView或者GridView --></LinearLayout>

list_itlem.xml


<?xmlversion="1.0" encoding="utf-8"?>
<RelativeLayout    xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><TextView        android:id="@+id/item"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_marginBottom="5px"android:layout_marginTop="5px"android:gravity="center"android:paddingBottom="25px"android:paddingTop="5px"android:textColor="#ff0000"android:textSize="60px"/><ImageView        android:id="@+id/imageItem"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_alignRight="@id/item"android:src="@mipmap/ic_launcher_round"/>
</RelativeLayout>

五、定义 RemoteViewsService 类


public class MyRemoteService extends RemoteViewsService {@Overridepublic RemoteViewsFactory onGetViewFactory(Intent intent) {return new MyRemoteViewsFactory(this.getApplicationContext(), intent);}
}

六、定义 RemoteViewsService.RemoteViewsFactory 实现类


public class MyRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {private final Context mContext;public static List<String> mList = new ArrayList<>();/** 构造函数*/public MyRemoteViewsFactory(Context context, Intent intent) {mContext = context;}/** MyRemoteViewsFactory调用时执行,这个方法执行时间超过20秒回报错。* 如果耗时长的任务应该在onDataSetChanged或者getViewAt中处理*/@Overridepublic void onCreate() {for (int i = 0; i < 5; i++) {mList.add("item" + i);}}/** 当调用notifyAppWidgetViewDataChanged方法时,触发这个方法* 例如:MyRemoteViewsFactory.notifyAppWidgetViewDataChanged();*/@Overridepublic void onDataSetChanged() {}/** 这个方法不用多说了把,这里写清理资源,释放内存的操作*/@Overridepublic void onDestroy() {mList.clear();}/** 返回集合视图数量*/@Overridepublic int getCount() {return mList.size();}/** 创建并且填充,在指定索引位置显示的View,这个和BaseAdapter的getView类似*/@Overridepublic RemoteViews getViewAt(int position) {if (position < 0 || position >= mList.size())return null;String content = mList.get(position);// 创建在当前索引位置要显示的Viewfinal RemoteViews rv = new RemoteViews(mContext.getPackageName(),R.layout.list_item);// 设置要显示的内容rv.setTextViewText(R.id.item, content);// 填充Intent,填充在AppWdigetProvider中创建的PendingIntentIntent intent = new Intent();// 传入点击行的数据intent.putExtra("content", content);rv.setOnClickFillInIntent(R.id.item, intent);return rv;}/** 显示一个"加载"View。返回null的时候将使用默认的View*/@Overridepublic RemoteViews getLoadingView() {return null;}/** 不同View定义的数量。默认为1(本人一直在使用默认值)*/@Overridepublic int getViewTypeCount() {return 1;}/** 返回当前索引的。*/@Overridepublic long getItemId(int position) {return position;}/** 如果每个项提供的ID是稳定的,即她们不会在运行时改变,就返回true(没用过。。。)*/@Overridepublic boolean hasStableIds() {return true;}
}

效果图

AppWidget “集合视图” 数据更新流程图

当widget指定其具体的AppWidgetProvider,AppWidgetProvider通过创建RemoteViews来加载视图,其RemoteViews将会调用setRemoteViewsAdapter来设置内部适配器,此适配器也将会继续获取widget管理器调用updateAppWidget()方法,此方法有会用远程视图工厂(RemoteViewsFactroy)来初始化数据并调用其onDataSetChanged()来通知适配器更新数据,具体更新那个widget的界面,是通过其GetViewAt将界面更新后并返回,其详细流程图如下:

总结

本系列Demo源码

本篇文章到此结束,欢迎关注,后续有补充的会即使更新,有问题也欢迎评论,共同成长

App Widgets 详解四 RemoteViews、RemoteViewsService和RemoteViewsFactory相关推荐

  1. 用Wex5平台打包生成App图文详解(Android)

    用Wex5平台打包生成App图文详解(Android) 第一步:到起步官网下载并解压好Wex5开发工具:http://www.wex5.com/downloads/ 第二步:在解压的目录下打开开发工具 ...

  2. App.Config详解

    App.Config详解 应用程序配置文件是标准的 XML 文件,XML 标记和属性是区分大小写的.它是可以按需要更改的,开发人员可以使用配置文件来更改设置,而不必重编译应用程序. 配置文件的根节点是 ...

  3. linux 进程间通信 dbus-glib【实例】详解四(上) C库 dbus-glib 使用(附代码)(编写接口描述文件.xml,dbus-binding-tool工具生成绑定文件)(列集散集函数)

    linux 进程间通信 dbus-glib[实例]详解一(附代码)(d-feet工具使用) linux 进程间通信 dbus-glib[实例]详解二(上) 消息和消息总线(附代码) linux 进程间 ...

  4. Android Studio 插件开发详解四:填坑

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78265540 本文出自[赵彦军的博客] 系列目录 Android Gradle使用 ...

  5. springboot 详解 (四)redis filter

    ---------------------------------------------------------------------------------------------------- ...

  6. 数据结构--图(Graph)详解(四)

    数据结构–图(Graph)详解(四) 文章目录 数据结构--图(Graph)详解(四) 一.图中几个NB的算法 1.普里姆算法(Prim算法)求最小生成树 2.克鲁斯卡尔算法(Kruskal算法)求最 ...

  7. .NET DLL 保护措施详解(四)各操作系统运行情况

    我准备了WEB应用程序及WinForm应用程序,分别在WIN SERVER 2012/2008/2003.Win7/10上实测,以下为实测结果截图: 2012 2008 2003 WIN7 WIN10 ...

  8. mybatis 鉴别其_MyBatis之Mapper XML 文件详解(四)-JDBC 类型和嵌套查询

    MyBatis之Mapper XML 文件详解(四)-JDBC 类型和嵌套查询 白玉 IT哈哈 支持的 JDBC 类型 为了未来的参考,MyBatis 通过包含的 jdbcType 枚举型,支持下面的 ...

  9. 【干货】某视频app推荐详解.pdf(附下载链接)

    今天给大家带来一份干货资料<某视频app推荐详解.pdf>,本文档包含推荐目标.推荐模型.推荐架构以及产品和运营相关的问题,文档已收录到小程序省时查报告中,大家可以到省时查报告小程序中查看 ...

最新文章

  1. C++中各种弹出对话框
  2. 深度学习在自然语言处理的应用
  3. 【网络安全】HTB靶机渗透系列之Sniper
  4. oracle中case when关键字的使用
  5. IntelliJ IDEA 12创建Maven管理的Java Web项目(图解)
  6. initializeBean()方法为容器产生的Bean 实例对象添加BeanPostProcessor 后置处理器
  7. C++文本操作.Vs.Python
  8. 汉诺塔问题深度剖析(python实现)
  9. .net框架读书笔记---虚方法
  10. 如何开启jvm日志_直通BAT必考题系列:JVM性能调优的6大步骤,及关键调优参数详解...
  11. android 查找所有dialog_android 布局文件layout分组的简单使用
  12. GoLand 远程开发配置
  13. linux+nginx+tomcat负载均衡,实现session同步
  14. python实现小说分割器
  15. 基于ryu实现网络的流量监控--monitor
  16. amd显卡用黑苹果输出黑屏_微星HD7850显卡DVI接口黑屏,改DSDT无果
  17. The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on .
  18. 【SVN】新旧服务器更替,完成svn服务器迁移
  19. Android 图片添加水印
  20. 北航操作系统课程-第一次作业-操作系统引论1

热门文章

  1. 计算机视觉期刊水平,计算机视觉和模式识别领域的SCI期刊菜鸡一枚,领域,识别,EditSprings,艾德思...
  2. 生物信息学基础知识Day2
  3. pascal过程与函数
  4. 10 个超好用的免费开源项目管理软件
  5. 全国计算机等级二级C语言上机编程题题型
  6. (13)TranslateMessage函数
  7. java j2c_将Java源代码转换为C++源代码的工具
  8. JAVASE、JAVAEE(J2EE)、
  9. 计算机内存坏了是什么反应,电脑内存损坏会引起哪些故障
  10. MATLAB 一幅图两个纵坐标(附带功能:设置字体、颜色、字号、坐标轴显示范围、显示间隔、加百分号)