App Widget

本文转自: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()方法,启动定义了一个用于启动ActivityPendingIntent对象,并且用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_SCREENWIDGET_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框架会忽略minWidthminHeightminResizeWidthminResizeHeight属性字段。如果该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。例如,GmailApp Widget

GridView

在一个二维的可滚动的网格中显示数据的View。例如,BookmarksApp Widget

StackView

一个堆放卡片的View(有点象关系网),用户可向下或向上来抽取卡片,以便分别的的看前一张或下一张卡片。例如,YouTubeBooks中包含的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,你必须要实现RemoteViewsServiceRemoteViewsFactory接口。

RemoteViewsService是一个服务,它允许远程的适配器来请求RemoteViews对象。

RemoteViewsFactory是一个在集合ViewView相关的数据之间的适配器接口,以下代码代码来自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));
    }
}

xml

<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" />

layout

<?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>
效果图

App Widget_第1张图片
AndroidManifest.xml

App Widget_第2张图片

你可能感兴趣的:(android,widget)