从零开始--系统深入学习android(实践-让我们开始写代码-Android框架学习-7.App Widgets)

第7章 App Widgets

App Widgets是一个应用程序的微型视图,可以嵌入到其他应用程序(如主屏幕)并且能够定期更新。你可以发布一个应用程序的App Widget,而这些视图称为窗口的用户界面。一个应用程序组件,可以支持其他应用程序的App Widgets称为App Widget的主机(host)。下面的截图是显示音乐的App Widget。

该文档将介绍如何在应用程序里发布和使用App Widget。

7.1 基础知识

要创建一个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。

7.2 在Manifest.xml中声明App Widgets

首先,在您的应用程序的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的资源位置。

7.3 添加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”。

7.4 创建App Widget布局

你必须为你的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>

 

7.5 使用AppWidgetProvider类

首先,你必须在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

7.6 创建一个App Widget配置的Activity

如果你想要一个用户,当他增加了一个新的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();

 

7.7 设置一个预览的图片

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资源下。


你可能感兴趣的:(从零开始--系统深入学习android(实践-让我们开始写代码-Android框架学习-7.App Widgets))