杀手的方式显示一个列表的项目在Android收集小部件
在早期版本的Android中,应用程序小部件只能显示像TextView
,ImageView
等。但是,如果我们想在小部件中显示项目列表,该怎么办呢?例如,显示整个下周的温度信息列表。Android 3.0中引入的集合小部件提供了额外的好处。集合小部件支持列表视图
,显示数据表格
而且StackView
布局。
今天,我将帮助您理解收集小部件是如何工作的。我们将为Todo应用程序构建一个应用程序小部件。集合小部件将用于显示待处理任务的列表。
我假设你已经知道如何制作一个基本的应用程序小部件。如果没有,请参考<一个href="//www.shaoxingby.com/how-to-code-an-android-widget/">这篇文章一个>当您准备好构建自己的集合小部件时再回来。
开始
请下载启动器项目代码<一个href="https://github.com/indiandollar/CollectionWidgetTutorial-Starter">在这里一个>我们将以此为基础。
代码已经实现了一个基本的小部件,我们将在同一个项目中创建一个集合小部件。基本小部件显示挂起任务的数量,集合小部件将显示完整的列表。要创建一个集合小部件,除了基本组件外,还需要两个主要组件:
RemoteViewsService
RemoteViewsFactory
让我们来了解这些组件的作用。
使用RemoteViewsFactory
RemoteViewsFactory
在小部件的上下文中充当适配器。适配器用于将集合项(例如,ListView项或GridView项)与数据集连接起来。
让我们将这个类添加到项目中。创建一个新的Java类,并命名它MyWidgetRemoteViewsFactory,并将其设置为实现该类RemoteViewsService。RemoteViewsFactory
.
公共类MyWidgetRemoteViewsFactory实现了RemoteViewsService.RemoteViewsFactory{私人上下文mContext;私人光标mCursor;公共MyWidgetRemoteViewsFactory(上下文applicationContext,意图意图){mContext=applicationContext;}@Override公共无效onCreate(){}@Override公共无效onDataSetChanged(){如果(mCursor! =零){mCursor.关闭();}最后长identityToken=粘结剂.clearCallingIdentity();Uriuri=合同.PATH_TODOS_URI;mCursor=mContext.getContentResolver().查询(uri,零,零,零,合同._ID+“DESC”);粘结剂.restoreCallingIdentity(identityToken);}@Override公共无效onDestroy(){如果(mCursor! =零){mCursor.关闭();}}@Override公共intgetCount(){返回mCursor= =零?0:mCursor.getCount();}@Override公共RemoteViewsgetViewAt(int位置){如果(位置= =AdapterView.INVALID_POSITION||mCursor= =零||!mCursor.moveToPosition(位置)){返回零;}RemoteViews房车=新RemoteViews(mContext.getPackageName(),R.布局.collection_widget_list_item);房车.setTextViewText(R.id.widgetItemTaskNameLabel,mCursor.getString(1));返回房车;}@Override公共RemoteViewsgetLoadingView(){返回零;}@Override公共intgetViewTypeCount(){返回1;}@Override公共长getItemId(int位置){返回mCursor.moveToPosition(位置)?mCursor.getLong(0):位置;}@Override公共布尔hasStableIds(){返回真正的;}}
在上面的代码中,MyWidgetRemoteViewsFactory方法中的一些方法RemoteViewsFactory
类:
onCreate
在第一次创建应用程序小部件时调用。onDataSetChanged
每当应用程序小部件更新时调用。getCount
返回游标中的记录数。(在本例中,需要在应用程序小部件中显示的任务项的数量)getViewAt
处理所有的处理工作。它返回一个RemoteViews
对象,在我们的例子中是单个列表项。getViewTypeCount
返回视图类型的数量列表视图
.在本例中,每个视图中都有相同的视图类型列表视图
所以我们返回1
在那里。
使用RemoteViewsService
的主要目的RemoteViewsService
返回一个RemoteViewsFactory
对象,该对象进一步处理用适当的数据填充小部件的任务。这门课没有太多内容。
创建一个名为MyWidgetRemoteViewsService扩展类RemoteViewsService
.
公共类MyWidgetRemoteViewsService扩展RemoteViewsService{@Override公共RemoteViewsFactoryonGetViewFactory(意图意图){返回新MyWidgetRemoteViewsFactory(这.getApplicationContext(),意图);}}
与android中的所有其他服务一样,我们必须在manifest文件中注册这个服务。
<服务android:的名字=".AppWidget.MyWidgetRemoteViewsService"android:许可="android.permission.BIND_REMOTEVIEWS">服务>
注意特殊权限android.permission.BIND_REMOTEVIEWS.这让系统绑定你的服务,为每一行创建小部件视图,并防止其他应用程序访问小部件的数据。
启动RemoteViewsService
既然我们已经设置了附加组件,现在是时候创建WidgetProvider
打电话给RemoteViewsService
.
在AppWidget包中创建一个新类并命名它CollectionAppWidgetProvider:
@Override公共无效onUpdate(上下文上下文,AppWidgetManagerappWidgetManager,int[]appWidgetIds){为(intappWidgetId:appWidgetIds){RemoteViews的观点=新RemoteViews(上下文.getPackageName(),R.布局.collection_widget);意图意图=新意图(上下文,MyWidgetRemoteViewsService.类);的观点.setRemoteAdapter(R.id.widgetListView,意图);appWidgetManager.updateAppWidget(appWidgetId,的观点);}}
创建小部件布局
现在在中创建一个新的资源文件res / xml给它命名collection_widget.xml.
在这个文件中,我们定义小部件设置,比如小部件应该使用哪个布局文件,并添加预览图像以获得更好的用户体验。
<appwidget-providerxmlns:安卓="http://schemas.android.com/apk/res/android"android:minWidth="40 dp"android:minHeight="40 dp"android:updatePeriodMillis="864000"android:previewImage="@drawable / simple_widget_preview"android:initialLayout="@layout / collection_widget"android:resizeMode="水平|垂直"android:widgetCategory="home_screen">appwidget-provider>
再创建一个资源文件,但这次在res /布局给它命名collection_widget.xml
在这个文件中,我们定义了要在集合小部件中显示的内容的布局。我们会在上面有一个标题,然后列表视图
在底部显示任务列表。
<LinearLayoutxmlns:安卓="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:背景="@color / colorWhite"xmlns:工具="http://schemas.android.com/tools"android:取向="垂直"><FrameLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:layout_width="match_parent"android:id="@ + id / widgetTitleLabel"android:文本="@string / title_collection_widget"android:输入textColor="@color / colorWhite"android:背景="@color / colorPrimary"android:textSize="18 dp"android:重力="中心"android:textAllCaps="真正的"android:layout_height="@dimen / widget_title_min_height">TextView>FrameLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><列表视图android:id="@ + id / widgetListView"android:layout_width="match_parent"android:layout_height="wrap_content"android:背景="@color / colorWhite"android:dividerHeight="1 dp"android:分频器="继续"工具:列="@layout / collection_widget_list_item">列表视图>LinearLayout>LinearLayout>
我们需要再创建一个文件res /布局定义每个列表项的布局。
创建这个文件并命名它collection_widget_list_item.xml
<LinearLayoutxmlns:安卓="http://schemas.android.com/apk/res/android"android:取向="水平"android:layout_width="match_parent"android:paddingLeft="@dimen / widget_listview_padding_x"android:paddingRight="@dimen / widget_listview_padding_x"android:paddingStart="@dimen / widget_listview_padding_x"android:paddingEnd="@dimen / widget_listview_padding_x"android:minHeight="@dimen / widget_listview_item_height"android:weightSum="2"android:id="@ + id / widgetItemContainer"android:layout_height="wrap_content"><TextViewandroid:id="@ + id / widgetItemTaskNameLabel"android:layout_width="wrap_content"android:重力="开始"android:layout_weight="1"android:输入textColor="@color /文本"android:layout_gravity="center_vertical"android:layout_height="wrap_content">TextView>LinearLayout>
现在运行应用程序,您应该能够看到小部件中填充了待办事项。(请确保重新安装应用程序以查看更改。你也可以禁用Android Studio中的即时运行选项)。
手动更新小部件
逻辑是这样的:每当您创建一个新的todo项时,您必须发送一个Broadcast到WidgetProvider
.
中定义一个新方法CollectionAppWidgetProvider类。
公共静态无效sendRefreshBroadcast(上下文上下文){意图意图=新意图(AppWidgetManager.ACTION_APPWIDGET_UPDATE);意图.setComponent(新ComponentName(上下文,CollectionAppWidgetProvider.类));上下文.sendBroadcast(意图);}
然后重写onReceive
方法中的CollectionAppWidgetProvider类,
@Override公共无效onReceive(最后上下文上下文,意图意图){最后字符串行动=意图.getAction();如果(行动.=(AppWidgetManager.ACTION_APPWIDGET_UPDATE)){//刷新所有小部件AppWidgetManager下=AppWidgetManager.getInstance(上下文);ComponentNamecn=新ComponentName(上下文,CollectionAppWidgetProvider.类);下.notifyAppWidgetViewDataChanged(下.getAppWidgetIds(cn),R.id.widgetListView);}超级.onReceive(上下文,意图);}
创建新的todo任务时,调用sendRefreshBroadcast
中定义的方法CollectionAppWidgetProvider类。
在MainActivity
,修改addTodoItem
相应的方法。
一个.runOnUiThread(新可运行的(){@Override公共无效运行(){烤面包.makeText(mContext,“新任务创建”,烤面包.LENGTH_LONG).显示();getTodoList();//这将发送广播来更新应用程序小部件CollectionAppWidgetProvider.sendRefreshBroadcast(mContext);}});
小部件中的事件处理
在小部件中,顶部有一个标题,底部有一个列表视图。因此,当用户单击标题时,我们启动应用程序。当在列表视图中单击单个项目时,我们启动details活动。在我们的todo应用中,细节活动可能没有那么有用,但让我们做它来理解这个概念。
在单个视图上单击事件
将单击事件添加到视图,如TextView
,ImageView
等都很简单。的更新代码onUpdate
方法。
@Override公共无效onUpdate(上下文上下文,AppWidgetManagerappWidgetManager,int[]appWidgetIds){为(intappWidgetId:appWidgetIds){RemoteViews的观点=新RemoteViews(上下文.getPackageName(),R.布局.collection_widget);//点击事件处理程序的标题,启动应用程序时,用户点击标题意图titleIntent=新意图(上下文,MainActivity.类);PendingIntenttitlePendingIntent=PendingIntent.getActivity(上下文,0,titleIntent,0);的观点.setOnClickPendingIntent(R.id.widgetTitleLabel,titlePendingIntent);意图意图=新意图(上下文,MyWidgetRemoteViewsService.类);的观点.setRemoteAdapter(R.id.widgetListView,意图);appWidgetManager.updateAppWidget(appWidgetId,的观点);}}
这里的想法类似于我们如何在应用程序中添加点击事件。但是由于小部件在不同的上下文中运行,我们需要通过PendingIntent
.
点击事件列表视图
项目
添加点击事件列表视图
项目并不像设置那么简单setOnItemClickListener
在列表视图
对象。它需要一些额外的步骤。
首先,您需要为PendingIntent
.将以下代码添加到onUpdate
方法CollectionAppWidgetProvider类后views.setRemoteAdapter (R.id。widgetListView,意图);
//模板处理每个项目的点击监听器意图clickIntentTemplate=新意图(上下文,DetailsActivity.类);PendingIntentclickPendingIntentTemplate=TaskStackBuilder.创建(上下文).addNextIntentWithParentStack(clickIntentTemplate).getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT);的观点.setPendingIntentTemplate(R.id.widgetListView,clickPendingIntentTemplate);
为每一个列表视图
我们正在推出的项目DetailsActivity
它将简单地显示作为额外内容发送的任务描述。
然后每次填写这个模板一个新的RemoteViews
对象创建的RemoteViewsFactory
.
将以下代码添加到getViewAt
方法MyWidgetRemoteViewsFactory类。
意图fillInIntent=新意图();fillInIntent.putExtra(CollectionAppWidgetProvider.EXTRA_LABEL,mCursor.getString(1));房车.setOnClickFillInIntent(R.id.widgetItemContainer,fillInIntent);
中定义的挂起意图模板CollectionAppWidgetProvider类。注意,我们想让整行都是可点击的,所以我们在根元素上设置了click监听器collection_widget_list_item.xml
结论
在本文中,我试图帮助解决初学者通常面临的最常见问题。如果你有任何问题,或者任何方法不适合你,请在下面的评论中告诉我。
您可以下载完整的工作代码<一个href="https://github.com/indiandollar/CollectionWidgetTutorial-TodoApp">在这里一个>.