本文转自:http://blog.csdn.net/think_soft/article/details/9285457
App Widgets是一些较小的应用程序窗口,它们能够被嵌入到其他的应用程序中(如主屏窗口),并且能够接受周期性的更新。这些小窗口可以作为用户界面中的一个可视部件,而且这些可视部件也可以带有自己的App Widget提供器。能够持有其他App Widgets的组件被叫做App Widget的持有者。以下截图是Music App Widget。
本文介绍如何发布一个使用App Widget提供器的App Widget。对于创建自己的AppWidgetHost来持有AppWidgets,请看AppWidget Host。
Widget设计
对于如何设计应用可视部件,请阅读Widget设计指南。
基础
以下是创建一个App Widget所要了解的基本内容:
1. AppWidgetProviderInfo对象
这个对象用于描述App Widget的元数据,如布局、更新周期以及AppWidgetProvider类等。它应该在XML文件中定义。
2. 实现AppWidgetProder类
这个类定义了一些App Widget所带有的基本的、基于广播事件的编程接口方法,通过这个类,当App Widget被更新、启用、禁用和删除时,你会收到广播事件。
3. 窗口布局
在XML中给App Widget定影初始化布局。
另外,你还可以实现一个用于App Widget配置的Activity。这是一个可选的Activity,它会在用户添加该App Widget时启动,并允许用户在创建是编辑App Widget的设置。
以下介绍如何创建App Widget。
在清单中声明一个AppWidget
首先,在你的应用程序的AndroidManifest.xml中声明AppWidgetProvider类,例如:
<receiverandroid:name="ExampleAppWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/example_appwidget_info" />
</receiver>
其中的<receiver>元素需要设置android:name属性,它会指定App Widget所使用的AppWidgetProvider类。
其中的<intent-filter>元素必须要包含一个带有android:name属性的<action>元素。这个属性指定AppWidgetProvider对象会接收ACTION_APPWIDGET_UPDATE类型的广播。这是需要你明确声明的唯一的广播。必要时,AppWidgetManager会自动的把所有其他的App Widget广播发送给AppWidgetProvider。
<meta-data>元素指定了AppWidgetProviderInfo对象所要求的资源,以及以下必要的属性:
android:name-指定元数据名称。使用android.appwidget.provider作为AppWidgetProviderInof的描述符来标识数据。
android:resource---指定AppWidgetProviderInfo资源的位置。
添加AppWidgetProviderInfo元数据
AppWidgetProviderInfo定义了App Widget的基本特点,例如它的最小布局尺寸、初始化布局资源、如何更新App Widget,以及创建时启动一个用于设置的Activity(可选)等。使用一个<appwidget-provider>元素在一个XML资源中定义AppWidgetProviderInfo对象,并把该资源保存在工程的res/xml文件夹中。
例如:
<appwidget-providerxmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp"
android:minHeight="40dp"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/preview"
android:initialLayout="@layout/example_appwidget"
android:configure="com.example.android.ExampleAppWidgetConfigure"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen|keyguard"
android:initialKeyguardLayout="@layout/example_keyguard">
</appwidget-provider>
以下是<appwidget-provider>元素属性的概要介绍
minWidth和minHeight属性值指定了AppWidget默认所要占据的空间大小。位于主屏窗口中的App Widget的尺寸是基于主屏中一个网格所定义的高度和宽度。如果App Widget的最小宽度或高度值跟网格的尺寸不匹配,那么App Widget的尺寸会四舍五入到最接近的单元格尺寸。有关App Widget尺寸的更多信息,请看AppWidget 设计指南。
注意:如果要让你的App Widget适应更多的设备,那么它的最小尺寸就不应该大于4x4单元格。
minResizeWidth和minResizeHeight属性指定了AppWidget的绝对最小尺寸。这两个值应该在不能确定App Widget尺寸的情况下使用,或者不使用。这个两个属性会允许用户重新调整Widget的尺寸,它们的值可以比minWidth和minHeight属性定义的默认的Widget尺寸值小。它们在Android3.1中被引入。
关于调整App Widget尺寸的更多信息,请看App Widget设计指南。
updatePeriodMillis属性定义了AppWidget框架的更新频率,App Widget通过调用onUpdate()回调方法来请求来自AppWidgetProvider的更新。实际的更新并不保证准确的按照这个值所定义的时间周期发生,并且我们建议尽可能减少更新频率---1小时最好不要超过一次,以便节省电池电量。你也可以通过配置允许用户来调整更新频率。有些人可能想要每隔15分钟看一次股票报价,也可能一天只看四次。
注意:如果设备在休眠时发生了更新,那么为了执行更新,设备将会被唤醒。如果每小时的更新不多于1次,那么就不会对电池的寿命造成显著的影响。但是,如果你要频繁的并且(或者)在设备休眠时不需要更新,那么你可以使用闹钟来代替,它不会唤醒设备。使用AlarmManager对象,给闹钟设置一个你的AppWidgetProvider能够接收的Intent对象。设置的闹钟类型既可以是ELAPSED_REALTIME也可以是RTC,它们只会在设备清醒的时候发送闹钟。然后把updatePeriodMillis属性设置为0.
initialLayout属性指定了定义App Widget布局的布局资源。
configure属性定义了用户添加App Widget时要启动的Activity,以便用于设置AppWidget属性。这是可选的。
previewImage属性指定了配置以后的App Widget的外观的预览图片,这样用户在选择该App Widget时就可以先看到外观。如果没有设置这个属性,那么用户就只会看到应用程序的启动图标。这个属性对应了AndroidManifest.xml文件中<receiver>元素中的android:previewImage属性。更多的使用previewImage讨论,请看设置预览图片。这个属性在Android3.0中被引入。
autoAdvanceViewId属性指定了AppWidget的子View的ID,它应该是有Widget的持有者自动生成的,它在Android3.0中被引入。
resizeMode属性指定调整Widget尺寸的规则。使用这个属性使得主屏Widget 可以调整尺寸。用户按住一个Widget就会显示一个调整尺寸的手柄,然后水平或垂直拖动手柄来改变Widget的尺寸。这个属性值包括:horizontal、vertical、none。要同时调整水平和垂直的尺寸,可以把属性值设置为“horizontal|vertical”。这个属性在Android3.1中被引入。
minResizeHeight属性指定了Widget可以调整的最小的高度(单位:dps)。如果它的值比minHeight属性值大,或者垂直尺寸不可调整,那么这个属性无效。该属性在Android4.0中被引入。
minResizeWidth属性指定了Widget可以调整的最小的宽度(单位:dps)。如果它的值比minWidth属性值大,或者水平尺寸不可调整,那么这个属性无效。该属性在Android4.0中被引入。
widgetCategory属性声明了你的App Widget是否能够显示在主屏或锁定屏幕上,或者是在这两个屏幕上都显示。它属性值包括:home_screen、keyguard。要让Widget同时显示在这种屏幕上,就要确保Widget类遵守它们的设计指南。更多的信息,请看“让App Widget能够显示在锁屏上”。默认值是“home_screen”。该属性在Android4.2中被引入。
InitialKeyguardLayout属性指向一个布局资源,这个布局资源定义了锁屏的App Widget布局。这个属性的工作方式跟android:initialLayout属性一样,它提供了一个能够立即显示的布局,直到App Widget初始化完成,并能够更新布局。该属性在Android4.2中被引入。
有关<appwidget-provider>元素所能接收的更多属性信息,请看AppWidgetProviderInfo类。
创建App Widget布局
你必须在XML文件中给你的App Widget定义一个初始布局,并把它保存在工程的res/layout目录中。你可以使用以下列出的View对象来设计你的App Widget,但是在开始设计之前,请阅读和理解AppWidget设计指南。
如果你熟悉布局,那么创建App Widget布局就很简单了。但是必须要注意的是:App Widget布局是基于RemoteViews,它不支持所有类型的布局和View。
RemoteView对象能够支持以下布局类:
· FrameLayout
· LinearLayout
· RelativeLayout
· GridLayout
以及以下可视组件类:
· AnalogClock
· Button
· Chronometer
· ImageButton
· ImageView
· ProgressBar
· TextView
· ViewFlipper
· ListView
· GridView
· StackView
· AdapterViewFlipper
这些类的派生类是不被支持的。
RemoteView类也提供了ViewStub,它是一个不可见的、尺寸为0的View,你可以使用它在运行时来慢慢的填充布局。
给App Widget添加边距
通常Widget不应该超出屏幕的边缘,而且在视觉上不应该与其他的Widget平齐,因此你应该在你的Widget所有边框周围添加边距。
从Android4.0开始,App Widget会自动的在Widget的边框和边界之间添加边距,以便更好的与其他的Widget和主屏上的图标对齐。要获取这种好处,强烈推荐你的应用程序所使用的SDK版本在14以上。
写一个能够适应早期版的有定制边距的单一布局,并且让它在Android4.0以后的版本中不会有额外的边距是很容的:
1.把应用程序的targetSdkVersion属性设置为14或更高的版本;
2.创建如下的布局,让它引用一个带有边距的尺寸资源:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/widget_margin">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:background="@drawable/my_widget_background">
…
</LinearLayout>
</FrameLayout>
3. 创建两个尺寸资源,一个在res/values/目录中,它给Android4.0之前的版本定制边距,另一个在res/values-v14/目录中,它给不给Android4.0以后的Widget提供额外的边距。
res/values/dimens.xml
<dimenname="widget_margin">8dp</dimen>
res/values-v14/dimens.xml
<dimenname="widget_margin">0dp</dimen>
另一种选择是简单的创建一个带有边距的默认的九宫格背景图,并且再给API Level 14以后的版本提供一个没有边距的九宫格背景图。
使用AppWidgetProvider类
AppWidgetProvider类继承BroadcastReceiver类,它能够方便的处理AppWidget广播。AppWidgetProvider只接收与App Widget相关的的事件广播,如App Widget被更新、被删除、被启用、以及被禁用。当这些广播事件发生时,AppWidgetProvider会接收以下方法的调用:
onUpdate()
在指定的时间间隔内调用这个方法来更新App Widget,这个时间间隔通过AppWidgetProviderInfo中的updatePeriodMillis属性来定义。当用户添加App Widget时这个方法也会被调用,因此它应该执行基本的安装,如给View定义事件处理器,以及如果需要,启动临时的Service。但是如果你声明了用于配置的Activity,那么当用户添加App Widget时,这个方法就不会被调用,但随后的更新会被调用。当配置完成后,配置Activity会负责执行第一次更新。
onAppWidgetOptionsChanged()
在第一次放置Widget时,以及在调整Widget尺寸的时候,这个方法就会被调用。你可以使用这个回调方法,基于Widget的尺寸来显示和隐藏内容。通过调用getAppWidgetOptionss()方法来获取Widget的尺寸范围,它会返回一个包含以下信息的Bundle对象:
1.OPTION_APPWIDGET_MIN_WIDTH---以dp为Widget距离单位,控制当前宽度的下限。
2.OPTION_APPWIDGET_MIN_HEIGHT---以dp为Widget距离单位,控制当前高度的下限。
3.OPTION_APPWIDGET_MAX_WIDTH---以dp为Widget距离单位,控制当前宽度的上限。
4.OPTION_APPWIDGET_MAX_HEIGHT---以dp为Widget距离单位,控制当前高度的上限。
这个回调方法在API Level 16(Android4.1)中被引入。如果你实现了这个回调方法,就要确保你的app不会对它形成依赖,因为在较早版本上这个方法不会被调用。
onDeleted(Context,int[])
每次从App Widget的持有者中删除该App Widget时,都会调用这个方法。
onEnabled(Context)
当第一次创建App Widget实例时,这个方法会被调用。例如,如果用户添加了两个相同的App Widget实例,那么这个方法只会在第一次创建App Widget实例时才会被调用。如果你需要打开一个新的数据库,或者那些只要执行一次的操作,那么使用这个回调方法时一个比较好的地方。
onDisabled(Context)
当从App Widget持有者中删除最后一个你的App Widget时,这个方法会被调用,在这个方法中,应该执行一些清理工作,如删除临时的数据库等。
onReceive(Context,Intent)
每个广播在上述的回调方法之前会调用这个方法,通常你不需要实现这个方法,因为默认的AppWidgetProvider实现了对所有的App Widget广播的过滤,并且会在适当的时候调用上述的回调方法。
最重要的AppWidgetProvider回调方法时onUpdate(),因为在把App Widget添加到持有者中时(除非你使用了一个配置Activity),都会调用这个方法。如果你的App Widget接收一些用户交互事件,那么你需要用这个回调方法来注册事件处理器。如果你的App Widget不创建临时文件或数据库、或其他的必要的清理工作,那么你可以只定义onUpdate()方法。例如,如果你想要点击App Widget中的一个按钮来启动一个Activity,你可以使用下面的AppWidgetProvider实现:
publicclassExampleAppWidgetProviderextendsAppWidgetProvider{
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
final int N = appWidgetIds.length;
// Performthis loop procedure for each App Widget that belongs to this provider
for (int i=0; i<N; i++) {
int appWidgetId = appWidgetIds[i];
// Create an Intent to launch ExampleActivity
Intent intent = new Intent(context, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
// Get the layout for the App Widget and attach an on-click listener
// to the button
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
views.setOnClickPendingIntent(R.id.button, pendingIntent);
// Tell the AppWidgetManager to perform an update on the current appwidget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
}
这个AppWidget只定义了onUpdate()方法,启动定义了一个用于启动Activity的PendingIntent对象,并且用setOnClickPendingIntent(int, PendingIntent)方法把它绑定到App Widget的按钮上。注意,onUpdate()方法中使用了一个循环来遍历appWidgetIds中的每个实体,appWidgetIds中包含了由提供器创建的每个App Widget实例的标识ID。用这种方法,如果用户创建了多个App Widget实例,那么所有实例的更新都是同时执行的。但是,只有一个updatePeriodMillis计划表来管理所有的App Widget实例。例如,如果更新计划被定义成每两小时一次,并且第二App Widget实例是在第一个之后一小时添加的,那么这两个实例都会使用第一个实例所定义的更新周期,而第二实例所定义的更新周期将会被忽略。
注意:因为AppWidgetProvider类继承BroadcastReceiver类,所以在回调方法返回之后,你的处理并不保证会保持运行的状态(有关广播生命周期的信息请看BroadcastReceiver类)。如果安装你的App Widget进程需要花费几秒钟的时间(可能是因为要执行一个Web请求),并且你要求你的处理要保持连续性,那么就要考虑在onUpdate()方法中启动一个Service。在这个Service中,你可以执行对App Widget的更新,而不用担心因应用无响应(ANR)的错误而关闭AppWidgetProvider。App Widget运行Service例子请看AppWidgetProvider示例。
接收App Widget广播的Intent对象
AppWidgetProvider只是一个便利的类,如果你想要直接接收App Widget广播,你可以实现自己的BroadcastReceiver类或重写onReceive(Context, Intent)回调方法。你需要关心以下类型的Intent对象:
ACTION_APPWIDGET_UPDATE
ACTION_APPWIDGET_DELETED
ACTION_APPWIDGET_ENABLED
ACTION_APPWIDGET_DISABLED
ACTION_APPWIDGET_OPTIONS_CHANGED
创建AppWidget的配置Activity
如果在用户添加一个新的App Widget时,你想要用户完成一些设置,那么你可以创建一个用于配置的Activity。这个Activity会由App Widget的持有者自动的启动,并允许用户在创建时给App Widget做一些有效的设置,如App Widget的颜色、大小、更新周期或其他的功能性设置。
这个配置Activity会作为一个普通的Activity在Android清单文件中声明。但是,它会由App Widget持有使用用ACTION_APPWIDGET_CONFIGURE操作来启动,因此这个Activity需要接受这个Intent对象。例如:
<activityandroid:name=".ExampleAppWidgetConfigure">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>
还有,这个Activity必须要在AppWidgetProviderInfo的XML文件中用android:configure属性来声明。例如:
<appwidget-providerxmlns:android="http://schemas.android.com/apk/res/android"
...
android:configure="com.example.android.ExampleAppWidgetConfigure"
... >
</appwidget-provider>
注意,这个Activity要使用完整的命名空间来声明,因为它会在你包范围之外被引用。
以上是启动带有配置Activity所要做的全部事情。现在需要实际的Activity。但是,在实现你的Activity时要记住两件重要的事情:
1. App Widget持有者会调用这个配置Activity,并且这个配置Activity要始终返回结果。这个结果应该包括App Widget的ID(它保存在Intent对象的附加信息中,键名:EXTRA_APPWIDGET_ID)。
2. 在AppWidget被创建时,onUpdate()方法不会被调用(因为在配置Activity被启动时,系统不会发送ACTION_APPWIDGET_UPDATE广播)。当App Widget被首次创建时,这个配置Activity会请求来自AppWidgetManager的更新。但是,onUpdate()方法会被后续的更新调用---它只是在第一次被跳过。
以下章节会看到从配置Activity返回结果和更新App Widget的代码片段。
从配置Activity中更新App Widget
当App Widget使用一个配置Activity时,在配置完成后,它会负责更行App Widget。直接向AppWidgetManager发送请求来完成更新。
以下是一个简要的更新App Widget并关闭配置Activity的过程:
1. 从启动Activity的Intent中获取App WidgetID:
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
}
2. 执行App Widget的配置
3. 配置完成时,通过调用getInstance(Context)方法来获取一个AppWidgetManager的实例:
AppWidgetManagerappWidgetManager =AppWidgetManager.getInstance(context);
4. 调用updateAppWidget(int,RemoteViews)方法来更新带有RemoteViews布局的App Widget:
RemoteViews views =newRemoteViews(context.getPackageName(),
R.layout.example_appwidget);
appWidgetManager.updateAppWidget(mAppWidgetId, views);
5. 最后创建一个要返回的Intent对象,设置返回结果,并销毁当前Activity:
Intent resultValue =newIntent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
提示:在你的配置Activity被首次打开时,把Activity的设置结果设置给RESULT_CANCELED。如果用户在设置结束之前退出了该Activity,那么用这种方法就会通知App Widget的持有者,配置被取消了,并且App Widget将不会被添加。
设置预览图片
Android3.0以后引入了previewImage字段,它用于指定AppWidget外观的预览图片,它会在Widget选择器中显示。如果不支持这个字段,那么App Widget的图片会用于预览。
以下是在XML中指定这个设置的方法:
<appwidget-providerxmlns:android="http://schemas.android.com/apk/res/android"
...
android:previewImage="@drawable/preview">
</appwidget-provider>
为了帮助你给App Widget创建预览图片,Android模拟器包含了一个叫做“Widget Preview”的应用程序。启动这个应用程序,给你的应用程序选择App Widget,并且给它创建要显示的预览图片,就可以创建一个App Widget的预览图片,然后保存它,并把它放到你的应用程序的可绘制资源中。
在锁屏上启用App Widget
Android4.2开始为用户引入了把Widget添加到锁屏上的能力。要指明你的App Widget在锁屏上是有效的,就要在XML文件中的AppWidgetProviderInfo中声明android:widgetCategory属性,这个属性支持两个值:”home_scree”和”keyguard”,一个App Widget能够声明支持其中之一或同时都支持。
默认情况下,每个App Widget都支持放到Home屏上,因此”home_screen”是android:widgetCategory属性的默认值。如果你想要App Widget在锁屏上也是有效的,就要添加”keyguard”属性值:
<appwidget-providerxmlns:android="http://schemas.android.com/apk/res/android"
...
android:widgetCategory="keyguard|home_screen">
</appwidget-provider>
如果你声明了一个Widget要同时显示在锁屏和主屏上,你会想到要根据显示的位置来定制Widget的外观。例如,你可能要给锁屏和主屏分别创建一个独立的布局文件。接下来要在运行时检查Widget的显示类别。通过调用getAppWidgetOptions()方法来获取一个Bundle对象,能够检查到你的Widget是要显示在锁屏还是主屏上。这个返回的Bundle对象会包含OPTION_APPWIDGET_HOST_CATEGORY健,它的值会是WIDGET_CATEGORY_HOME_SCREEN或WIDGET_APPWIDGET_HOST_CATEGORY之一。这个值被用于判断该Widget的持有者。在AppWidgetProvider中,你可以检查该Widget的种类,例如:
AppWidgetManager appWidgetManager;
int widgetId;
Bundle myOptions = appWidgetManager.getAppWidgetOptions(widgetId);
// Get the value of OPTION_APPWIDGET_HOST_CATEGORY
int category = myOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1);
// If the value is WIDGET_CATEGORY_KEYGUARD, it's a lockscreenwidget
boolean isKeyguard = category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;
一旦你知道了Widget的种类,你就可以有选择的装载不同的布局,设置不同的属性等,例如:
int baseLayout =isKeyguard ? R.layout.keyguard_widget_layout: R.layout.widget_layout;
你还可以用android:initalKeyguardLayout属性给你的App Widget指定在锁屏上的初始布局。这个属性的工作方法与android:initialLayout属性相同,它提供了一个可以立即显示的布局,直到App Widget被初始化并可以更新布局。
尺寸调整指南
当一个Widget被锁屏所持有时,Android框架会忽略minWidth、minHeight、minResizeWidth和minResizeHeight属性字段。如果该Widget同时也可以放置到主屏上,那么这些属性字段依然需要,因为在主屏上会使用它们,但是针对锁屏它们会被忽略。
锁屏Widget的宽度始终是填充在被提供的空间中,它的高度有以下选项:
1. 如果Widget没有标记它的垂直尺寸可调(android:resizeMode=”vertical”),那么它的高度将始终是”small”模式:
在电话的纵向模式中,”small”被定义为显示解锁UI后的剩余空间;
在平板和横向的电话中,”small”是每个设备的基本设置。
2. 如果Widget被标记为垂直尺寸可调,那么在显示解锁UI的纵向电话上,Widget的高度会使用”small”模式。在所有其他情况下,Widget会调整高度以便填充到可用的高度。
使用带有集合的App Widget
Android3.0以后引入了带有集合的App Widget。这些类型的App Widget使用RemoteViewService来显示由远程数据所返回的数据集合,如来自contentprovider的数据。由RemoteViewsService提供的数据会被显示在下列类型之一的View中,我们把这些View叫做“集合View”:
ListView
在一个垂直滚动的列表中显示数据的View。例如,Gmail的App Widget
GridView
在一个二维的可滚动的网格中显示数据的View。例如,Bookmarks的App Widget
StackView
一个堆放卡片的View(有点象关系网),用户可向下或向上来抽取卡片,以便分别的的看前一张或下一张卡片。例如,YouTube和Books中包含的App Widget。
AdapterViewFilpper
一个支持简单ViewAnimator的适配器View,它可以在两个或更多的View之间产生动画。每次只显示一个子View。
在上述的View中,会显示由远程数据所返回的数据集。这就意味着它们要使用一个Adapter把数据跟用户界面绑定。Adapter会把数据集中的每个数据项绑定到每个View对象上。因为这些集合View是由Adapter所支持的,所以Android框架必须包含额外的架构来支持在App Widget中使用它们。在App Widget的内容中,Adapter会被RemoteViewsFactory替代,它只是简单的封装了Adapter的接口。当请求集合中的一个特殊项目时,RemoteViewsFactory会给集合创建并返回一个RemoteViews对象。为了在你的App Widget中包含一个集合View,你必须要实现RemoteViewsService和RemoteViewsFactory接口。
RemoteViewsService是一个服务,它允许远程的适配器来请求RemoteViews对象。
RemoteViewsFactory是一个在集合View和View相关的数据之间的适配器接口,以下代码代码来自StackView Widget示例,它是你用于实现服务和接口的样板代码:
publicclassStackWidgetServiceextendsRemoteViewsService{
@Override
public RemoteViewsFactoryonGetViewFactory(Intent intent) {
return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
}
}
class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
//... include adapter-like methods here. See the StackViewWidget sample.
}
Calculator中Widget的实现
public class CalculatorWidget extends AppWidgetProvider { public static final String DIGIT_0 = "0"; public static final String DIGIT_1 = "1"; public static final String DIGIT_2 = "2"; public static final String DIGIT_3 = "3"; public static final String DIGIT_4 = "4"; public static final String DIGIT_5 = "5"; public static final String DIGIT_6 = "6"; public static final String DIGIT_7 = "7"; public static final String DIGIT_8 = "8"; public static final String DIGIT_9 = "9"; public static final String DOT = "dot"; public static final String PLUS = "plus"; public static final String MINUS = "minus"; public static final String MUL = "mul"; public static final String DIV = "div"; public static final String EQUALS = "equals"; public static final String CLR = "clear"; private static final StringBuilder equation = new StringBuilder(); @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { RemoteViews remoteViews; ComponentName calcWidget; remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget); calcWidget = new ComponentName(context, CalculatorWidget.class); setOnClickListeners(context, remoteViews); remoteViews.setTextViewText(R.id.display, equation.toString()); appWidgetManager.updateAppWidget(calcWidget, remoteViews); } @Override public void onReceive(Context context, Intent intent) { if(equation.toString().equals(context.getResources().getString(R.string.error))) equation.setLength(0); if(intent.getAction().equals(DIGIT_0)) { equation.append(0); } else if(intent.getAction().equals(DIGIT_1)) { equation.append(1); } else if(intent.getAction().equals(DIGIT_2)) { equation.append(2); } else if(intent.getAction().equals(DIGIT_3)) { equation.append(3); } else if(intent.getAction().equals(DIGIT_4)) { equation.append(4); } else if(intent.getAction().equals(DIGIT_5)) { equation.append(5); } else if(intent.getAction().equals(DIGIT_6)) { equation.append(6); } else if(intent.getAction().equals(DIGIT_7)) { equation.append(7); } else if(intent.getAction().equals(DIGIT_8)) { equation.append(8); } else if(intent.getAction().equals(DIGIT_9)) { equation.append(9); } else if(intent.getAction().equals(DOT)) { equation.append(context.getResources().getString(R.string.dot)); } else if(intent.getAction().equals(DIV)) { equation.append(context.getResources().getString(R.string.div)); } else if(intent.getAction().equals(MUL)) { equation.append(context.getResources().getString(R.string.mul)); } else if(intent.getAction().equals(MINUS)) { equation.append(context.getResources().getString(R.string.minus)); } else if(intent.getAction().equals(PLUS)) { equation.append(context.getResources().getString(R.string.plus)); } else if(intent.getAction().equals(EQUALS)) { final String input = equation.toString(); final Logic mLogic = new Logic(context); mLogic.setLineLength(7); if(input.isEmpty()) return; equation.setLength(0); try { equation.append(mLogic.evaluate(input)); } catch(SyntaxException e) { equation.append(context.getResources().getString(R.string.error)); } } else if(intent.getAction().equals(CLR)) { equation.setLength(0); } ComponentName calcWidget = new ComponentName(context, CalculatorWidget.class); RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget); remoteViews.setTextViewText(R.id.display, equation.toString()); setOnClickListeners(context, remoteViews); AppWidgetManager.getInstance(context).updateAppWidget(calcWidget, remoteViews); super.onReceive(context, intent); } private void setOnClickListeners(Context context, RemoteViews remoteViews) { final Intent intent = new Intent(context, CalculatorWidget.class); intent.setAction(DIGIT_0); remoteViews.setOnClickPendingIntent(R.id.digit0, PendingIntent.getBroadcast(context, 0, intent, 0)); intent.setAction(DIGIT_1); remoteViews.setOnClickPendingIntent(R.id.digit1, PendingIntent.getBroadcast(context, 1, intent, 0)); intent.setAction(DIGIT_2); remoteViews.setOnClickPendingIntent(R.id.digit2, PendingIntent.getBroadcast(context, 2, intent, 0)); intent.setAction(DIGIT_3); remoteViews.setOnClickPendingIntent(R.id.digit3, PendingIntent.getBroadcast(context, 3, intent, 0)); intent.setAction(DIGIT_4); remoteViews.setOnClickPendingIntent(R.id.digit4, PendingIntent.getBroadcast(context, 4, intent, 0)); intent.setAction(DIGIT_5); remoteViews.setOnClickPendingIntent(R.id.digit5, PendingIntent.getBroadcast(context, 5, intent, 0)); intent.setAction(DIGIT_6); remoteViews.setOnClickPendingIntent(R.id.digit6, PendingIntent.getBroadcast(context, 6, intent, 0)); intent.setAction(DIGIT_7); remoteViews.setOnClickPendingIntent(R.id.digit7, PendingIntent.getBroadcast(context, 7, intent, 0)); intent.setAction(DIGIT_8); remoteViews.setOnClickPendingIntent(R.id.digit8, PendingIntent.getBroadcast(context, 8, intent, 0)); intent.setAction(DIGIT_9); remoteViews.setOnClickPendingIntent(R.id.digit9, PendingIntent.getBroadcast(context, 9, intent, 0)); intent.setAction(DOT); remoteViews.setOnClickPendingIntent(R.id.dot, PendingIntent.getBroadcast(context, 10, intent, 0)); intent.setAction(DIV); remoteViews.setOnClickPendingIntent(R.id.div, PendingIntent.getBroadcast(context, 11, intent, 0)); intent.setAction(MUL); remoteViews.setOnClickPendingIntent(R.id.mul, PendingIntent.getBroadcast(context, 12, intent, 0)); intent.setAction(MINUS); remoteViews.setOnClickPendingIntent(R.id.minus, PendingIntent.getBroadcast(context, 13, intent, 0)); intent.setAction(PLUS); remoteViews.setOnClickPendingIntent(R.id.plus, PendingIntent.getBroadcast(context, 14, intent, 0)); intent.setAction(EQUALS); remoteViews.setOnClickPendingIntent(R.id.equal, PendingIntent.getBroadcast(context, 15, intent, 0)); intent.setAction(CLR); remoteViews.setOnClickPendingIntent(R.id.clear, PendingIntent.getBroadcast(context, 16, intent, 0)); } }
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="250dp" android:minHeight="250dp" android:initialLayout="@layout/widget" android:initialKeyguardLayout="@layout/widget" android:updatePeriodMillis="0" android:widgetCategory="home_screen|keyguard" android:resizeMode="vertical" />
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="match_parent" android:background="@color/background" android:layout_width="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:minHeight="60dp" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:id="@+id/display" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:gravity="right|center_vertical" style="@style/display_style" /> <Button android:id="@+id/clear" android:text="@string/clear" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="3" android:textSize="20dp" style="@style/button_style" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" > <Button android:id="@+id/digit7" android:text="@string/digit7" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/digit_button_style" /> <Button android:id="@+id/digit8" android:text="@string/digit8" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/digit_button_style" /> <Button android:id="@+id/digit9" android:text="@string/digit9" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/digit_button_style" /> <Button android:id="@+id/div" android:text="@string/div" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/button_style" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" > <Button android:id="@+id/digit4" android:text="@string/digit4" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/digit_button_style" /> <Button android:id="@+id/digit5" android:text="@string/digit5" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/digit_button_style" /> <Button android:id="@+id/digit6" android:text="@string/digit6" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/digit_button_style" /> <Button android:id="@+id/mul" android:text="@string/mul" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/button_style" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" > <Button android:id="@+id/digit1" android:text="@string/digit1" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/digit_button_style" /> <Button android:id="@+id/digit2" android:text="@string/digit2" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/digit_button_style" /> <Button android:id="@+id/digit3" android:text="@string/digit3" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/digit_button_style" /> <Button android:id="@+id/minus" android:text="@string/minus" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/button_style" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" > <Button android:id="@+id/dot" android:text="@string/dot" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/digit_button_style" /> <Button android:id="@+id/digit0" android:text="@string/digit0" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/digit_button_style" /> <Button android:id="@+id/equal" android:text="@string/equal" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/button_style" /> <Button android:id="@+id/plus" android:text="@string/plus" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" style="@style/button_style" /> </LinearLayout> </LinearLayout> </LinearLayout>效果图