App Widgets是一个应用程序的微型视图,可以嵌入到其他应用程序(如主屏幕)并且能够定期更新。你可以发布一个应用程序的App Widget,而这些视图称为窗口的用户界面。一个应用程序组件,可以支持其他应用程序的App Widgets称为App Widget的主机(host)。下面的截图是显示音乐的App Widget。
该文档将介绍如何在应用程序里发布和使用App Widget。
要创建一个App Widget,您需要了解以下几点:
◆AppWidgetProviderInfo对象:
描述了一个App Widget的元数据,如在App Widget的布局,更新频率,和AppWidgetProvider类。都应在XML中定义。
◆AppWidgetProvider类的实现:
定义一个基于广播事件与App Widget的接口方法。通过它,您将收到广播对App Widget进行更新,启用,禁用和删除。
◆视图布局:
在XML中初步定义App Widget布局。
此外,还可以实现App Widget可配置的Activity。当用户添加您的App Widget,并允许他或她在创建时修改设置时启动这个可配置的Activity。
该文档将介绍如何在应用程序里发布和使用App Widget。
首先,在您的应用程序的AndroidManifest.xml文件中应声明AppWidgetProvider类。例如代码清单7-1所示:
<receiver android: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>
代码清单 7-1
<receiver>节点需要的android:name属性,在App Widget中指定使用AppWidgetProvider。 <intent-filter>节点必须包含一个<action>节点的name属性。此属性指定AppWidgetProvider接受ACTION_APPWIDGET_UPDATE广播。这是唯一的广播,你必须明确声明。在AppWidgetManager自动发送其他App widget广播到AppWidgetProvider是必要的。<meta-data>节点指定的AppWidgetProviderInfo的资源,需要以下属性:
android:name - 指定的元数据的名称。使用android.appwidget.provider识别作为
◆AppWidgetProviderInfo描述符的数据。
◆android:resource - 指定的AppWidgetProviderInfo的资源位置。
AppWidgetProviderInfo是定义App Widget的本质,例如其最小的布局尺寸,初始布局资源,如何更新App Widget,和(可选)配置Activity,在创建时发起。在XML资源文件中定义AppWidgetProviderInfo对象,使用<appwidget-provider>节点和在项目的res / xml/文件夹中保存。如代码清单7-2所示:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="294dp" android:minHeight="72dp" android:updatePeriodMillis="86400000" android:previewImage="@drawable/preview" android:initialLayout="@layout/example_appwidget" android:configure="com.example.android.ExampleAppWidgetConfigure" android:resizeMode="horizontal|vertical"> </appwidget-provider>
代码清单 7-2
以下是<appwidget-provider>属性的摘要,在这之前最好你已经阅读了第一部分的Widget设计章节:
◆App Widget默认的情况下minWidth和MinHeight属性值指定最小占据的空间,AppWidgets默认是在Home屏幕位置,在其窗口基础上的单元格中有一个明确高度和宽度的网格。如果一个App Widget的最小宽度或高度值不匹配单元格的尺寸,则App Widget尺寸向上舍入到最接近的单元格大小。
注:为了使App Widget更容易移植在不同设备,App Widget的最小尺寸不应大于4×4单元格
◆minResizeWidth和minResizeHeight属性指定App Widget的绝对最小尺寸。这些值应该指定尺寸下,否则应用程序组件将无法辨认或以其他方式使用。在android3.1,允许用户使用这些属性调整控件大小,可能是小于默认尺寸界定的minwidth和minheight属性。
◆updateperiodmillis属性定义,往往在App Widget框架,应从appwidgetprovider请求通过调用onupdate()回调方法更新。实际上不能保证更新的准确性,我们建议尽可能的少更新或者不超过每小时一次,以此来节省电池。你也可以在configuration-some中允许用户调整频率,比如证劵报价机,一些用户可能想要15分钟更新一次,一些则想要每天只更新四次
注:如果该设备是睡着的,而它是一个更新的时间(定义updatePeriodMillis)时,则该设备将被唤醒以执行更新。如果你不超过每小时更新一次,这可能不会对电池寿命造成重大的问题。但是,如果您需要频繁更新或你并不需要更新,而设备是睡着的,你就可以根据报警代替执行,则不会唤醒设备执行更新。要做到这一点,设置一个Intent,当您的AppWidgetProvider收到的报警时,使用AlarmManager。设置报警类型有ELAPSED_REALTIME或RTC,这在收到报警时,该设备被唤醒。然后设置updatePeriodMillis为零(“0”)。
◆initialLayout的属性指向布局资源,它定义了App Widget的布局。
◆在Activity启动时对属性进行配置定义,用户添加App Widget,以便让用户配置App Widget属性。这是可选的。
◆在配置previewImage属性后将指定一个App Widget图标是什么样子,当选择这个App Widget时用户可以进行预览。如果没有提供图标,用户却认为laucher是您的应用程序图标。这个字段对应android:previewImage进行在 <receiver>元素的AndroidManifest.xml文件中。在android3.0引入。
◆在Android 3.0引入,该autoAdvanceViewId属性指定的App Widget子视图的视图ID。
◆在Android 3.1引入,该resizeMode属性指定其中一个可以调整规则的Widget。您可以使用此属性使主屏幕Widget的调整方式,如水平,垂直,或两轴。用户长按一个Widget,会显示其调整的界面,然后拖动水平和/或垂直的控键,改变布局网格的大小。resizemode属性值包括"horizontal", "vertical", 和"none"。两者都有如“horizontal | vertical”。
你必须为你的App widget定义初始布局,你可以在XML定义并保存在项目的res/layout/目录中。你可以使用下列的View对象来设计你的App widget,但在你开始设计你的App widget之前,请阅读和理解App widget的设计准则。 如果你熟悉XML的布局,创建App widget的布局很简单。然而,你们必须知道App widget的布局都是基于RemoteViews类,它不支持各种布局或view widget。
一个RemoteViews对象支持以下布局类:
FrameLayout
LinearLayout
RelativeLayout
以下View支持widget:
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
但是这些类的子类却都不支持。
1. 添加边距到App Widgets
widget通常不应该扩展到屏幕边缘,不应与其他widget视图共同刷新,所以你应该在你的widget框中增加边距。自Android 4.0起,App widget提供了widget之间的自动填充框架和App widget的包围盒,以便用户在home屏幕更好的调整其他widget和图标。要获得这种功能,你需要吧应用程序的targetSdkVersion设置为14或更高。
早期版本,编写一个布局很容易,并可以自定义边距,在Android4.0或以上版本并没有额外的边距,步骤如下:
◆设置应用程序的targetsdkversion值为14或更高。
◆创建一个如下布局,引用dimension资源,如代码清单7-3所示:
<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>
代码清单 7-3
◆创建两个dimensions 的资源,一个在res /values/提供Android 4.0之前的自定义边距,一个在res/values-v14/没有为Android4.0widgets提供额外的padding:
res/values/dimens.xml:
<dimen name="widget_margin">8dp</dimen>
res/values-v14/dimens.xml:
<dimen name="widget_margin">0dp</dimen>
首先,你必须在AndroidManifest<receiver>节点里声明 的AppWidgetProvider类的实现(参见本章的“7.2在Manifest.xml中声明App Widgets” )。
AppWidgetProvider继承broadcastreceiver用来处理App widget广播非常方便。 AppWidgetProvider只接收和App widget相关的事件广播,如App widget进行更新时,相关的App widget进行删除,启用和禁用。这些广播事件发生时,AppWidgetProvider将调用以下的方法:
◆onUpdate()
这种在AppWidgetProviderInfo由updatePeriodMillis属性定义的时间间隔来使AppWidget更新。当用户添加App widget时,这种方法也被调用,所以它应该进行基本的设置,如定义View事件的处理,如果有必要,还应启动临时service。不过,如果你已经声明配置的Activity,当用户添加App widge这种方法则不会调用,而是后续更新调用。配置的Activity完成后,它的作用就是执行首次更新。
◆onAppWidgetOptionsChanged()
当wodget第一次被调整时被调用。你能使用这个回调来显示或隐藏内容。你可以通过getAppWidgetOptions()来获得大小范围,它返回一个Bundle,你可以使用下面String键,来获得值:
AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH—当前宽度的最小值,单位是DP
AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT—当前高度的最小值,单位是DP
AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH—当前宽度的最大值,单位是DP
AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT—当前高度的最大值,单位是DP
Android4.1从在引入这个方法。请注意
◆onDeleted(Context, int[])
每次一个App Widget从App Widget主机中删除时被调用。
◆onEnabled(Context)
首次创建App widget实例时调用。例如,如果用户为同一个App widget添加了两个实例,这也只调用一次。如果你需要打开一个新的数据库或进行其他的设置,那么在这个地方实例是非常好的。
◆onDisabled(Context)
当最后一个App widget的实例时从App widget主机中删除时被调用,使用onDisabled(Context)方法进行清理,比如删除临时数据库。
◆onReceive(Context, Intent)
可以理解为一个通用广播接收接口,上面的每个方法的回调。你通常不需要实现这个方法,因为默认的AppWidgetProvider实现过滤器所有App widget广播,并适当调用以上的方法。
当每个App widget添加到一个主机时,最重要的是AppWidgetProvider onUpdate()方法回调(除非你使用一个配置的Activity)。如果你的App widget接受任何用户交互事件,那么在回调时,你需要注册事件处理器。如果你的App widget不能创建临时文件或数据库,或者执行其他的工作,那就需要清楚,onUpdate()方法,可能是你唯一需要定义的回调方法。例如,如果你想要一个App widget上有一个按钮,当点击时启动一个Activity,你可以这样实现AppWidgetProvider,如代码清单7-4所示:
public class ExampleAppWidgetProvider extends AppWidgetProvider { public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { final int N = appWidgetIds.length; for (int i=0; i<N; i++) { int appWidgetId = appWidgetIds[i]; Intent intent = new Intent(context, ExampleActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout); views.setOnClickPendingIntent(R.id.button, pendingIntent); appWidgetManager.updateAppWidget(appWidgetId, views); } } }
代码清单 7-4
上面代码清单7-4中的AppWidgetProvider只定义onupdate()方法,其目的是定义一个PendingIntent启动一个Activity并使用使用setonclickpendingintent(int,pendingintent)附加到App widget按钮。注意,在appWidgetIds中它包括一个循环遍历每个条目,这是一个数组的id标识,确定每个App widget。这样,如果用户创建多个App widget的实例,然后他们都同时更新。然而,只有一个updateperiodmillis时间表将管理所有的App widget。例如,如果更新计划被定义为每两个小时,在第一个后等待一小时在添加第二个实例,那么它们两个都将使用第一个的周期而第二个更新周期会被忽略。读者可以参考ApiDemos\src\com\example\android\apis\appwidget的例子。
AppWidgetProvider就是一个方便的类而已。如果你想直接接收App widget广播,你可以实现自己的BroadcastReceiver或覆盖的onReceive(Context, Intent) 方法。你需要注意以下几个intent:
ACTION_APPWIDGET_UPDATE
ACTION_APPWIDGET_DELETED
ACTION_APPWIDGET_ENABLED
ACTION_APPWIDGET_DISABLED
ACTION_APPWIDGET_OPTIONS_CHANGED
如果你想要一个用户,当他增加了一个新的App widget时来配置设置,那么你可以创建一个App widget配置Activity。当前的Acitivity将自动启动的App widget的主机,并允许用户在创建时配置App widget的颜色,大小,更新周期或其他功能的设置。 这个配置Activity应该在Android manifest文件中声明是一个标准的Activity。然而,它将通过App widget主机使用ACTION_APPWIDGET_CONFIGURE Action来启动,所以这个Activity需要接收这种Intent。如代码清单7-5所示:
<activity android:name=".ExampleAppWidgetConfigure"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/> </intent-filter> </activity>
代码清单 7-5
此外,Activity必须在AppWidgetProviderInfo XML文件中声明android:configure属性(参见前面小节“添加AppWidgetProviderInfo元数据”)。例如,需要配置的Activity声明如代码清单7-6所示:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" ... android:configure="com.example.android.ExampleAppWidgetConfigure" ... > </appwidget-provider>
代码清单 7-6
注意,这个Activity是声明完全限定的命名空间,因为它是从外部包引用的。
你需要的是启动一个配置Activity。现在你所需要的是实际的Activity。然而,当你实现Activity有两个重要的事情要记住:
◆这个App widget主机调用配置Activity,而配置Activity应该总是返回一个结果码。这个结果码应该包括App widget ID。
◆当创建App widget时OnUpdate()方法将不会被调用(配置Activity启动时,系统将不发送ACTION_APPWIDGET_UPDATE广播)。
这是配置Activity的职责,它请求从App widget首次创建AppWidgetManager时更新。然而,onUpdate()方法将调用后续更新,它仅在第一次跳过。
请参阅以下的代码片段,看它怎样返回配置和更新的App widget后的结果。
当一个App widget使用配置Activity时,这个Activity配置完成后负责更新App widget。你可以从AppWidgetManager通过请求直接更新。
1. 从通过Intent启动的Activity中获得App widget的ID:
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实例:
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
4. 通过调用updateAppWidget(int,RemoteViews)来使用RemoteViews布局更新App widget:
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.example_appwidget); appWidgetManager.updateAppWidget(mAppWidgetId, views);
5. 最后,创建返回的intent,其设置Activity的结果,并终止该Activity:
Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); setResult(RESULT_OK, resultValue); finish();
Android3.0之后引入了previewImage属性,它指定一个Appwidget缩略图。下面让我们看下代码清单7-7,看看是如何设置的
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" ... android:previewImage="@drawable/preview"> </appwidget-provider>
代码清单 7-7
为了帮助您的Appwidget(指定在previewImage领域)创建预览图像,在Android模拟器中包含一个应用程序被称为“Widget Preview”。要创建预览图像,则要启动该应用程序,选择你的应用程序的Appwidget,并设置你希望的预览图像,然后将它保存,并将其放置在你的应用程序的drawable资源下。