App Widgets 详解四 RemoteViews、RemoteViewsService和RemoteViewsFactory
来源:互联网 发布:记忆枕 知乎 编辑:程序博客网 时间:2024/06/16 20:37
导读
本篇文章将介绍”集合视图”,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更新”集合视图”的一般步骤是:
通过RemoteViews.setRemoteAdapter(R.id.ListView_ID,Service 的 intent)来设置 “RemoteViews对应RemoteViewsService”
在RemoteViewsService中,实现RemoteViews对应RemoteViewsService.RemoteViewsFactory接口.
在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); // 创建一个RemoteView RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.my_remote_widget); // 把这个Widget绑定到RemoteViewsService Intent 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); // 更新Widget appWidgetManager.updateAppWidget(appWidgetId, remoteViews); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // There may be multiple widgets active, so update all of them for (int appWidgetId : appWidgetIds) { updateAppWidget(context, appWidgetManager, appWidgetId); } } @Override public void onEnabled(Context context) { // Enter relevant functionality for when the first widget is created Toast.makeText(context, "用户将widget添加桌面了", Toast.LENGTH_SHORT).show(); } @Override public void onDisabled(Context context) { // Enter relevant functionality for when the last widget is disabled } @Override public void onDeleted(Context context, int[] appWidgetIds) { Toast.makeText(context, "用户将widget从桌面移除了", Toast.LENGTH_SHORT).show(); super.onDeleted(context, appWidgetIds); } /** * 接受Intent * * @param context * @param intent */ @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); String action = intent.getAction(); if (action.equals("refresh")) { int i = 0; // 刷新Widget final 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(); } }}
==注意==
- RemoteViews.setEmptyView() 设置空视图必须是集合视图的兄弟节点,空视图表示空状态 (没数据时设置空视图??)
- 当我们使用集合视图,如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
<?xml version="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 { @Override public 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中处理 */ @Override public void onCreate() { for (int i = 0; i < 5; i++) { mList.add("item" + i); } } /* * 当调用notifyAppWidgetViewDataChanged方法时,触发这个方法 * 例如:MyRemoteViewsFactory.notifyAppWidgetViewDataChanged(); */ @Override public void onDataSetChanged() { } /* * 这个方法不用多说了把,这里写清理资源,释放内存的操作 */ @Override public void onDestroy() { mList.clear(); } /* * 返回集合视图数量 */ @Override public int getCount() { return mList.size(); } /* * 创建并且填充,在指定索引位置显示的View,这个和BaseAdapter的getView类似 */ @Override public RemoteViews getViewAt(int position) { if (position < 0 || position >= mList.size()) return null; String content = mList.get(position); // 创建在当前索引位置要显示的View final RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.list_item); // 设置要显示的内容 rv.setTextViewText(R.id.item, content); // 填充Intent,填充在AppWdigetProvider中创建的PendingIntent Intent intent = new Intent(); // 传入点击行的数据 intent.putExtra("content", content); rv.setOnClickFillInIntent(R.id.item, intent); return rv; } /* * 显示一个"加载"View。返回null的时候将使用默认的View */ @Override public RemoteViews getLoadingView() { return null; } /* * 不同View定义的数量。默认为1(本人一直在使用默认值) */ @Override public int getViewTypeCount() { return 1; } /* * 返回当前索引的。 */ @Override public long getItemId(int position) { return position; } /* * 如果每个项提供的ID是稳定的,即她们不会在运行时改变,就返回true(没用过。。。) */ @Override public boolean hasStableIds() { return true; }}
效果图
AppWidget “集合视图” 数据更新流程图
当widget指定其具体的AppWidgetProvider,AppWidgetProvider通过创建RemoteViews来加载视图,其RemoteViews将会调用setRemoteViewsAdapter来设置内部适配器,此适配器也将会继续获取widget管理器调用updateAppWidget()方法,此方法有会用远程视图工厂(RemoteViewsFactroy)来初始化数据并调用其onDataSetChanged()来通知适配器更新数据,具体更新那个widget的界面,是通过其GetViewAt将界面更新后并返回,其详细流程图如下:
总结
本系列Demo源码
本篇文章到此结束,欢迎关注,后续有补充的会即使更新,有问题也欢迎评论,共同成长
- App Widgets 详解四 RemoteViews、RemoteViewsService和RemoteViewsFactory
- RemoteViewsService.RemoteViewsFactory
- App Widgets 详解三 Activity中添加App Widgets
- 在widget实现复杂布局(Listview,GirdView)以及RemoteViewsService、RemoteViewsFactory的用法
- RemoteviewsService
- RemoteViews详解
- App Widgets 详解一 简单使用
- App Widgets 详解二 Configuration Activity
- App widgets
- App Widgets
- App Widgets
- App Widgets
- Android App Widgets
- android App Widgets
- Android-App Widgets
- Android App widgets
- App Widgets的使用
- Android Developer - App Widgets
- resource fork, Finder information, or similar detritus not allowed
- Android和Kotlin结合
- iOS学习笔记之正则表达式详解
- 从程序员到CTO的Java技术路线图
- CondaHTTPError: HTTP None None for url <https://repo.continuum.io/pkgs/free/osx-64/repodata...
- App Widgets 详解四 RemoteViews、RemoteViewsService和RemoteViewsFactory
- 深度学习笔记——深度学习框架TensorFlow(六)[TensorFlow线性模型教程]
- PHP中的命名空间(namespace)的使用
- 2、(二)外汇学习基础篇之银行间外汇市场的相关定义
- 2017.6.27 树上操作 思考记录
- C语言 商业贷款计算器 等额本金 等额本息
- Hibernate与 MyBatis的比较
- Hibernate基础(2)
- C++ 冒泡排序