【Android必备】构建一个App小部件(24)【代码块异常】

概要


应用小部件是微型应用程序视图,可嵌入其他应用程序(例如主屏幕)并接收定期更新。这些视图在用户界面中称为小部件,您可以使用App Widget提供程序发布视图。一个能够容纳其他App Widget的应用程序组件被称为App Widget主机。下面的截图显示了音乐应用小部件。
【Android必备】构建一个App小部件(24)【代码块异常】

本文档介绍如何使用App Widget提供程序发布App Widget。有关创建自己的AppWidgetHost 应用程序窗口小部件的讨论,请参阅 应用程序窗口小部件主机。

注意: 有关如何设计应用程序小部件的信息,请阅读应用程序小部件概述。

基础


要创建App Widget,您需要以下内容:

AppWidgetProviderInfo 目的
描述App Widget的元数据,例如App Widget的布局,更新频率和AppWidgetProvider类。这应该在XML中定义。

AppWidgetProvider 类实现
定义允许您基于广播事件以编程方式与App Widget进行接口的基本方法。通过它,您将在App Widget更新,启用,禁用和删除时收到广播。

查看布局
定义用Widget定义的App Widget的初始布局。
另外,您可以实现App Widget配置活动。这是一个可选项 Activity,当用户添加App Widget时允许他或她在创建时修改App Widget设置。

以下各节介绍如何设置每个组件。

在清单中声明App Widget


首先,AppWidgetProvider在您的应用程序AndroidManifest.xml文件中声明该类 。例如:

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

该<receiver>元素需要android:name 属性,该属性指定AppWidgetProviderApp Widget使用的 属性。

该<intent-filter>元素必须包含<action> 具有该android:name属性的 元素。该属性指定AppWidgetProvider接受ACTION_APPWIDGET_UPDATE广播。这是您必须明确声明的唯一广播。根据需要AppWidgetManager 自动将所有其他App Widget广播发送到AppWidgetProvider。

该<meta-data>元素指定的 AppWidgetProviderInfo资源,需要以下属性:

  • android:name - 指定元数据名称。使用 android.appwidget.provider 标识数据的AppWidgetProviderInfo 描述符。
  • android:resource- 指定AppWidgetProviderInfo 资源位置。

添加AppWidgetProviderInfo元数据


所述AppWidgetProviderInfo一个应用程序的widget,的基本品质定义如它的最小布局尺寸,其初始布局资源,多久更新应用窗口小部件,和(任选地)一个配置Activity推出在创建时间。使用单个<appwidget-provider>元素在XML资源中定义AppWidgetProviderInfo对象, 并将其保存在项目 res/xml/ 文件夹中。

例如:

<appwidget-provider xmlns: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">
</appwidget-provider>

以下是<appwidget-provider>属性的摘要:

  • 这些minWidth和minHeight 属性的值指定了App Widget 默认使用的最小空间量 。默认的主屏幕根据具有定义的高度和宽度的单元网格在其窗口中定位App Widgets。如果App Widget的最小宽度或高度值与单元格的尺寸不匹配,则App Widget尺寸向上舍 入到最接近的单元格尺寸。
  • 有关调整App Widgets大小的更多信息,请参阅 App Widget设计指南。
  • 将minResizeWidth和minResizeHeight属性指定应用Widget的绝对最小尺寸。这些值应指定App Widget难以辨认或以其他方式无法使用的大小。使用这些属性允许用户将小部件大小调整为可能小于minWidth和由minHeight属性定义的默认小部件大小的大小 。在Android 3.1中引入。
  • 有关调整App Widgets大小的更多信息,请参阅 App Widget设计指南。
  • 该updatePeriodMillis属性定义了App Widget框架应该AppWidgetProvider通过调用 onUpdate() 回调方法请求更新的频率。实际更新不能保证按照此值准确发生,我们建议尽可能不要更新 - 可能每小时不要超过一次,以节省电池。您也可以允许用户调整配置中的频率 - 有些人可能希望股票报价器每15分钟更新一次,或者每天只能更新四次。
  • 该initialLayout属性指向定义App Widget布局的布局资源。
  • 该configure属性定义在Activity用户添加App Widget时启动,以便他或她配置App Widget属性。这是可选的(请阅读下面的创建应用程序Widget配置活动)。
  • 该previewImage属性指定应用程序窗口小部件配置后的外观样式的预览,用户在选择应用窗口小部件时会看到该样式。如果未提供,用户会看到您的应用程序的启动器图标。该字段对应 于文件中元素的 android:previewImage属性。有关使用的更多讨论,请参阅设置预览图像。在Android 3.0中引入。<receiver>AndroidManifest.xmlpreviewImage
  • 该autoAdvanceViewId属性指定应由widget的主机自动进阶的app widget子视图的视图ID。在Android 3.0中引入。
  • 该resizeMode属性指定可以调整窗口小部件的规则。您可以使用此属性来调整主屏幕小部件的大小 - 水平,垂直或双轴。用户触摸按住小部件以显示其大小调整手柄,然后拖动水平和/或垂直手柄以更改布局网格上的大小。resizeMode属性的值 包括“水平”,“垂直”和“无”。要声明一个小部件可以水平和垂直调整大小,请提供值“horizo​​ntal | vertical”。在Android 3.1中引入。
  • 该minResizeHeight属性指定小部件可以调整到的最小高度(以dps为单位)。如果此字段大于minHeight或未启用垂直调整大小,则此字段无效(请参阅resizeMode)。在Android 4.0中引入。
  • 该 minResizeWidth 属性指定小部件可以调整大小的最小宽度(以dps为单位)。如果该字段大于minWidth或未启用水平调整大小,则该字段无效(请参阅resizeMode)。在Android 4.0中引入。
  • 该widgetCategory属性声明您的App Widget是否可以显示在主屏幕(home_screen),锁屏(keyguard)或两者上。只有低于5.0的Android版本才支持锁屏小部件。对于Android 5.0及更高版本,仅限home_screen有效。

有关元素AppWidgetProviderInfo接受的属性的更多信息,请参阅该类<appwidget-provider>。

创建App Widget布局


您必须使用XML定义您的App Widget的初始布局,并将其保存在项目的 res/layout/目录中。您可以使用下面列出的View对象来设计App Widget,但在开始设计App Widget之前,请阅读并理解 App Widget设计指南。

如果您熟悉布局,创建App Widget布局很简单。但是,您必须知道,App Widget布局基于RemoteViews,不支持任何类型的布局或视图窗口小部件。

RemoteViews对象(以及因此的App Widget)可以支持以下布局类:

  • FrameLayout
  • LinearLayout
  • RelativeLayout
  • GridLayout
    以下小部件类:

  • AnalogClock
  • Button
  • Chronometer
  • ImageButton
  • ImageView
  • ProgressBar
  • TextView
  • ViewFlipper
  • ListView
  • GridView
  • StackView
  • AdapterViewFlipper

不支持这些类的后代。

RemoteViews还支持ViewStub,这是一个不可见的零大小的视图,您可以使用它在运行时延迟布局资源。

为App Widgets添加边距

小部件通常不应该扩展到屏幕边缘,并且不应该在视觉上与其他小部件齐平,所以您应该在小部件框架的周围添加边距。

从Android 4.0开始,应用程序窗口小部件会自动在窗口小部件框架和应用程序窗口小部件的边框之间填充,以便与用户主屏幕上的其他窗口小部件和图标更好地对齐。要利用这种强烈推荐的行为,请将应用程序的targetSdkVersion设置为14或更大。

编写一个单独的布局很容易,该布局具有适用于早期版本平台的自定义边距,并且对于Android 4.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/用于提供Android 4.0之前的自定义边距,另一个res/values-v14/用于为Android 4.0 widgets提供额外的填充:
res / values / dimens.xml:

< dimen name = “widget_margin” > 8dp </ dimen > 

res / values-v14 / dimens.xml:

< dimen name = “widget_margin” > 0dp </ dimen > 

另一种选择是在默认情况下简单地为您的九块补丁背景资产构建额外的边距,并为API等级14或更高版本提供不具有边距的不同九块补丁。

使用AppWidgetProvider类


本AppWidgetProvider类广播接收器作为一个方便的类来处理应用的Widget广播延伸。AppWidgetProvider只接收与App Widget相关的事件广播,例如App Widget更新,删除,启用和禁用时。当这些广播事件发生时,AppWidgetProvider会收到以下方法调用:

onUpdate()
这被称为以updatePeriodMillis AppWidgetProviderInfo中属性定义的间隔更新App Widget (请参阅上面的添加AppWidgetProviderInfo元数据)。当用户添加App Widget时也会调用此方法,因此它应该执行必要的设置,例如为Views定义事件处理程序,并Service在必要时启动临时设置 。但是,如果您声明了配置Activity,则在用户添加App Widget时不会调用此方法,但会为后续更新调用此方法。配置完成后,执行第一次更新是配置活动的责任。(请参阅下面的创建应用程序小部件配置活动。)

onAppWidgetOptionsChanged()
这是在小部件第一次放置时以及小部件调整大小时调用的。您可以使用此回调来显示或隐藏基于窗口小部件大小范围的内容。您通过调用获取大小范围getAppWidgetOptions(),返回 Bundle包含以下内容的大小范围:

  • OPTION_APPWIDGET_MIN_WIDTH - 以小部件实例的当前宽度(以dp为单位)包含下限。
  • OPTION_APPWIDGET_MIN_HEIGHT - 以小部件实例的当前高度(以dp为单位)包含下限。
  • OPTION_APPWIDGET_MAX_WIDTH - 以小部件实例的当前宽度(以dp为单位)包含上限。
  • OPTION_APPWIDGET_MAX_HEIGHT - 以小部件实例的当前高度(以dp为单位)包含上限。
    此回调是在API级别16(Android 4.1)中引入的。如果您实现此回调,请确保您的应用不依赖于它,因为它不会在较旧的设备上调用。

onDeleted(Context, int[])
每次从App Widget主机中删除App Widget时都会调用它。
onEnabled(Context)
这是在第一次创建App Widget的实例时调用的。例如,如果用户添加了App Widget的两个实例,这只是第一次。如果您需要打开新的数据库或执行其他设置,只需要为所有App Widget实例执行一次,那么这是一个很好的选择。
onDisabled(Context)
当您的App Widget的最后一个实例从App Widget主机中被删除时,这将被调用。这是您应该清理在其中完成的任何工作的位置 onEnabled(Context),例如删除临时数据库。
onReceive(Context, Intent)
这被称为每个广播和每个上述回调方法之前。您通常不需要实现此方法,因为默认的AppWidgetProvider实现会过滤所有App Widget广播并根据需要调用上述方法。

您必须使用<receiver>AndroidManifest中的元素将您的AppWidgetProvider类实现声明为广播接收器(请参阅 上面的清单中的声明应用程序Widget)。

最重要的AppWidgetProvider回调函数是 onUpdate() 因为每次将App Widget添加到主机时都会调用它(除非使用配置Activity)。如果您的App Widget接受任何用户交互事件,则需要在此回调中注册事件处理程序。如果您的App Widget不创建临时文件或数据库,或执行其他需要清理的工作,则 onUpdate() 可能是您需要定义的唯一回调方法。例如,如果您想要一个带有按钮的App Widget,单击该按钮可启动一个Activity,则可以使用以下AppWidgetProvider实现:

public class ExampleAppWidgetProvider extends AppWidgetProvider {
 public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
     final int N = appWidgetIds.length; 

【Android必备】构建一个App小部件(24)【代码块异常】

    // 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 app widget 
            appWidgetManager.updateAppWidget(appWidgetId, views);
        } 
    } 
} 

这个AppWidgetProvider仅仅 onUpdate() 定义了一个方法来定义一个方法PendingIntent来启动Activity并附加到App Widget的按钮setOnClickPendingIntent(int, PendingIntent)。请注意,它包含一个遍历每个条目的循环 appWidgetIds,这是一个ID数组,用于标识由此提供者创建的每个App Widget。这样,如果用户创建了多个App Widget实例,那么它们都会同时更新。但是,只有一个updatePeriodMillis计划将针对App Widget的所有实例进行管理。例如,如果更新计划定义为每两个小时,并且App Widget的第二个实例在第一个实例后一小时添加,那么它们都将按第一个和第二个更新定义的时间段进行更新时期将被忽略(他们都会每两小时更新一次,而不是每小时更新一次)。

注意:因为AppWidgetProvider是扩展名BroadcastReceiver,所以在回调方法返回后,您的进程不保证继续运行(请参阅BroadcastReceiver有关广播生命周期的信息)。如果您的App Widget安装过程可能需要几秒钟的时间(可能在执行Web请求时),并且您需要继续进行,请考虑Service在该 onUpdate() 方法中启动一个。从服务内部,您可以对App Widget执行自己的更新,而无需担心AppWidgetProvider由于应用程序不响应(ANR)错误而关闭。有关运行a的App Widget示例,请参阅Wiktionary示例的AppWidgetProviderService。

另请参阅ExampleAppWidgetProvider.java 示例类。

接收App Widget广播意图

AppWidgetProvider只是一个便利的课程。如果您想直接接收App Widget广播,则可以实现自己的 BroadcastReceiver或覆盖 onReceive(Context, Intent)回调。你需要关心的意图如下:

  • ACTION_APPWIDGET_UPDATE
  • ACTION_APPWIDGET_DELETED
  • ACTION_APPWIDGET_ENABLED
  • ACTION_APPWIDGET_DISABLED
  • ACTION_APPWIDGET_OPTIONS_CHANGED

固定应用小部件


在运行Android 8.0(API级别26)及更高版本的设备上,允许您创建固定快捷方式的启动器还允许您将应用程序控件固定到启动器上。与固定的快捷方式类似,这些固定的小部件可让用户访问您应用中的特定任务。

在您的应用程序中,您可以创建一个系统请求,通过完成以下步骤顺序将小部件固定到受支持的启动器上:

在应用程序的清单文件中创建小部件,如以下代码片段所示:

<manifest>
...
  <application>
    ...
    <receiver android:name="MyAppWidgetProvider">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data android:name="android.appwidget.provider"
                   android:resource="@xml/my_appwidget_info" />
    </receiver>
  </application>
</manifest>

调用该 requestPinAppWidget()方法,如以下代码片段所示:

AppWidgetManager mAppWidgetManager =
        context.getSystemService(AppWidgetManager.class);
ComponentName myProvider =
        new ComponentName(context, MyAppWidgetProvider.class);

if (mAppWidgetManager.isRequestPinAppWidgetSupported()) {
    // Create the PendingIntent object only if your app needs to be notified
    // that the user allowed the widget to be pinned. Note that, if the pinning
    // operation fails, your app isn't notified.
    Intent pinnedWidgetCallbackIntent = new Intent( ... );

    // Configure the intent so that your app's broadcast receiver gets
    // the callback successfully. This callback receives the ID of the
    // newly-pinned widget (EXTRA_APPWIDGET_ID).
    PendingIntent successCallback = PendingIntent.createBroadcast(context, 0,
            pinnedWidgetCallbackIntent);

    mAppWidgetManager.requestPinAppWidget(myProvider, null, successCallback);
}

注意:如果您的应用不需要通知系统是否成功将小部件固定到受支持的启动器上,则可以将其null作为第三个参数 传入requestPinAppWidget()。

创建App Widget配置活动


如果您希望用户在添加新App Widget时配置设置,则可以创建App Widget配置活动。这Activity 将由App Widget主机自动启动,并允许用户在创建时配置App Widget的可用设置,例如App Widget的颜色,大小,更新周期或其他功能设置。

配置Activity应该在Android清单文件中声明为普通的Activity。但是,该操作将由App Widget主机启动ACTION_APPWIDGET_CONFIGURE,因此该活动需要接受此Intent。例如:

<activity android:name=".ExampleAppWidgetConfigure">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
    </intent-filter>
</activity>

此外,必须在AppWidgetProviderInfo XML文件中使用该android:configure属性声明该Activity (请参阅上面的添加AppWidgetProviderInfo元数据)。例如,配置Activity可以像这样声明:

    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
            ...
            android:configure="com.example.android.ExampleAppWidgetConfigure"
            ... >
    </appwidget-provider>

请注意,该活动是使用完全限定的名称空间声明的,因为它将从包的范围之外引用。

这就是您开始使用配置活动所需的一切。现在你需要的是实际的活动。但是,实施活动时需要记住两件重要的事情:

  • App Widget主机调用配置Activity,配置Activity应始终返回结果。结果应该包括启动Activity的Intent传递的App Widget ID(保存在Intent extras中 EXTRA_APPWIDGET_ID)。
  • App Widget创建时不会调用该 onUpdate() 方法(启动配置Activity时,系统不会发送ACTION_APPWIDGET_UPDATE广播)。当App Widget第一次创建时,配置Activity负责从AppWidgetManager请求更新。但是, 将被要求进行后续更新 - 它仅在第一次跳过。onUpdate()

有关如何从配置中返回结果并更新App Widget的示例,请参阅以下部分中的代码片段。

从配置Activity更新App Widget

当App Widget使用配置Activity时,活动有责任在配置完成时更新App Widget。您可以通过直接请求更新来完成此操作 AppWidgetManager。

以下是正确更新App Widget并关闭配置Activity的过程摘要:

  1. 首先,从启动Activity的Intent获取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. RemoteViews通过调用updateAppWidget(int, RemoteViews)以下布局来 更新App Widget :

    RemoteViews views = new RemoteViews(context.getPackageName(),
    R.layout.example_appwidget);
    appWidgetManager.updateAppWidget(mAppWidgetId, views);
  5. 最后,创建返回意图,将其设置为活动结果,并完成活动:
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();

提示:首次打开您的配置活动时,请将活动结果设置为RESULT_CANCELED以及EXTRA_APPWIDGET_ID,如上面的步骤5所示。这样,如果用户在到达结尾之前退出Activity,则会通知App Widget主机配置已取消,并且不会添加App Widget。

以ApiDemos中的ExampleAppWidgetConfigure.java示例类为例。

设置预览图像


Android 3.0引入了该previewImage字段,该字段指定应用小部件的外观样式。该预览会从窗口小部件选取器向用户显示。如果未提供此字段,则应用程序小部件的图标用于预览。

这是您在XML中指定此设置的方式:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
  ...
  android:previewImage="@drawable/preview">
</appwidget-provider>

为了帮助为您的应用部件创建预览图像(在previewImage字段中指定),Android模拟器包含一个名为“Widget Preview”的应用程序。要创建预览图像,请启动此应用程序,为您的应用程序选择应用程序小部件,并设置预览图像的显示方式,然后保存并将其放入应用程序的可绘制资源中。

在集合中使用App Widget


Android 3.0引入了应用程序小部件和集合。这些类型的App Widget使用它RemoteViewsService来显示由远程数据(例如来自内容提供者)支持的集合。通过RemoteViewsService 使用以下视图类型之一在应用程序窗口小部件中提供的数据,我们将其称为“集合视图:”

ListView
显示垂直滚动列表中的项目的视图。有关示例,请参阅Gmail应用程序小部件。
GridView
显示二维滚动网格中的项目的视图。有关示例,请参阅书签应用程序小部件。
StackView
堆叠式卡片视图(有点像rolodex),用户可以分别上下滑动前端卡片以查看前一张/下一张卡片。示例包括YouTube和图书应用小部件。
AdapterViewFlipper
适配器支持的简单 ViewAnimator动画在两个或多个视图之间进行动画处理。一次只显示一个孩子。
如上所述,这些集合视图显示由远程数据支持的集合。这意味着他们使用一个Adapter将他们的用户界面绑定到他们的数据。一个Adapter结合从一组数据转换成单独的个别项目View的对象。由于这些集合视图由适配器支持,因此Android框架必须包含额外的架构以支持其在应用程序小部件中的使用。在应用程序窗口小部件的上下文中,将Adapter被替换为a RemoteViewsFactory,这只是Adapter 界面周围的一个薄包装。当在集合中请求特定项目时,RemoteViewsFactory创建集合的项目并将其作为RemoteViews 对象返回。为了在您的应用程序小部件中包含集合视图,您必须实现RemoteViewsService和RemoteViewsFactory。

RemoteViewsService是一种允许远程适配器请求RemoteViews对象的服务。RemoteViewsFactory是集合视图(例如ListView,GridView等)与该视图的基础数据之间适配器的接口。从 StackView Widget示例中,下面是用于实现此服务和接口的样板代码示例:

public class StackWidgetService extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
    }
}

class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {

//... include adapter-like methods here. See the StackView Widget sample.

}

示例应用程序
本节中的代码摘录来自StackView Widget示例:

【Android必备】构建一个App小部件(24)【代码块异常】
该样品由10个视图,其显示值的堆叠的 "0!"通过"9!"将样品应用部件有这些主要行为:

  • 用户可以垂直拖动应用程序小部件中的顶部视图以显示下一个或上一个视图。这是一个内置的StackView行为。
  • 在没有任何用户交互的情况下,应用程序窗口小部件会按顺序自动前进,如幻灯片放映。这是由于设置 android:autoAdvanceViewId="@id/stack_view"了在 res/xml/stackwidgetinfo.xml文件中。该设置适用于视图ID,在这种情况下,视图ID是堆栈视图的视图ID。
  • 如果用户触摸顶视图,应用小部件将显示Toast消息“触摸视图n ”,其中 n是触摸视图的索引(位置)。有关如何实施的更多讨论,请参阅 将行为添加到单个项目。

使用集合实现应用程序小部件

要使用集合实现应用程序窗口小部件,您需要遵循与用于实现任何应用程序窗口小部件相同的基本步骤。以下各节介绍了您需要执行的附加步骤来实现具有集合的应用程序窗口小部件。

适用于具有集合的应用程序小部件的清单
除了在清单中声明应用程序小部件中列出的要求外,为了使应用程序小部件可以与集合绑定到您的应用程序小部件RemoteViewsService,您必须在清单文件中使用该许可声明该服务BIND_REMOTEVIEWS。这可以防止其他应用程序自由访问您的应用程序小部件的数据 例如,在创建RemoteViewsService用于填充集合视图的App Widget时,清单条目可能如下所示:

<service android:name="MyWidgetService"
...
android:permission="android.permission.BIND_REMOTEVIEWS" />

该行android:name="MyWidgetService" 指向你的子类RemoteViewsService。

具有集合的应用程序小部件的布局
为您的应用程序窗口小部件的布局XML文件中的主要要求是,它包括收集意见之一:ListView, GridView,StackView,或 AdapterViewFlipper。这里是 widget_layout.xml为StackView窗口小部件样本:

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <StackView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/stack_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:loopViews="true" />
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/empty_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:background="@drawable/widget_item_background"
        android:textColor="#ffffff"
        android:textStyle="bold"
        android:text="@string/empty_view_text"
        android:textSize="20sp" />
</FrameLayout>

请注意,空视图必须是集合视图的同胞,其中空视图代表空状态。

除了整个应用程序小部件的布局文件之外,还必须创建另一个布局文件来定义集合中每个项目的布局(例如,书籍集合中每本书的布局)。例如,StackView Widget示例只有一个布局文件widget_item.xml,因为所有项目都使用相同的布局。但 WeatherListWidget示例有两个布局文件: dark_widget_item.xml和light_widget_item.xml。

AppWidgetProvider类用于具有集合的应用程序窗口小部件
与常规应用程序小部件一样,您的AppWidgetProvider子类中的大部分代码通常都会进入onUpdate()。onUpdate()使用集合创建应用程序窗口小部件时,您的实现方式的主要区别在于您必须进行调用setRemoteAdapter()。这告诉集合视图从哪里获取数据。然后,RemoteViewsService可以返回您的实现RemoteViewsFactory,并且小部件可以提供适当的数据。当你调用这个方法时,你必须传递一个指向你的实现的意图RemoteViewsService和指定要更新的应用部件的应用部件ID。

例如,以下是StackView Widget示例如何实现onUpdate()回调方法以将RemoteViewsService应用程序构件集合设置为远程适配器:

代码块异常

坚持数据

如上所述,你的RemoteViewsService子类提供了RemoteViewsFactory用来填充远程集合视图。

具体而言,您需要执行以下步骤:

  • 子类RemoteViewsService。RemoteViewsService是远程适配器可以通过其请求的服务RemoteViews。
  • 在你的RemoteViewsService子类中,包含一个实现RemoteViewsFactory 接口的类。RemoteViewsFactory是远程集合视图(例如ListView,GridView等)与该视图的基础数据之间的适配器接口。您的实施负责为RemoteViews数据集中的每个项目创建一个对象。这个接口是一个薄薄的包装Adapter。

您不能依赖您的服务的单个实例或其包含的任何数据来坚持。因此,你不应该在你的数据中存储任何数据RemoteViewsService(除非它是静态的)。如果您希望应用程序小部件的数据持续存在,最好的方法是使用 ContentProvider其数据持续超出流程生命周期的数据。

实施的主要内容RemoteViewsService 是RemoteViewsFactory,如下所述。

RemoteViewsFactory接口

实现RemoteViewsFactory 接口的自定义类为应用程序小部件提供了其集合中项目的数据。为此,它将您的应用程序窗口项目XML布局文件与数据源结合在一起。这个数据源可以是从数据库到简单数组的任何东西。在 StackView Widget示例中,数据源是一个数组WidgetItems。RemoteViewsFactory 作为将数据粘贴到远程集合视图的适配器的功能。

你需要为你的RemoteViewsFactory 子类实现的两个最重要的方法 是 onCreate()和 getViewAt() 。

系统onCreate()在首次创建工厂时调用。这是您为数据源设置任何连接和/或游标的地方。例如,StackView Widget示例用于onCreate()初始化一个WidgetItem对象数组。当您的应用程序小部件处于活动状态时,系统将使用它们在数组中的索引位置访问这些对象,并显示它们包含的文本。

以下是StackView Widget 示例 RemoteViewsFactory实现的摘录,其中显示了该onCreate() 方法的各个部分:

    class StackRemoteViewsFactory implements
    RemoteViewsService.RemoteViewsFactory {
            private static final int mCount = 10;
            private List<WidgetItem> mWidgetItems = new ArrayList<WidgetItem>();
            private Context mContext;
            private int mAppWidgetId;

            public StackRemoteViewsFactory(Context context, Intent intent) {
                    mContext = context;
                    mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                                    AppWidgetManager.INVALID_APPWIDGET_ID);
            }

            public void onCreate() {
                    // In onCreate() you setup any connections / cursors to your data source. Heavy lifting,
                    // for example downloading or creating content etc, should be deferred to onDataSetChanged()
                    // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.

该RemoteViewsFactory方法getViewAt() 返回与数据集中RemoteViews指定position的数据相对应的对象。以下是 StackView Widget示例RemoteViewsFactory 实现的摘录:

public RemoteViews getViewAt(int position) {

    // Construct a remote views item based on the app widget item XML file,
    // and set the text based on the position.
    RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);
    rv.setTextViewText(R.id.widget_item, mWidgetItems.get(position).text);

    ...
    // Return the remote views object.
    return rv;
}

将行为添加到单个项目
以上部分向您展示了如何将数据绑定到您的应用部件集合。但是,如果您想要将动态行为添加到集合视图中的单个项目呢?

如使用AppWidgetProvider类所述,通常用于setOnClickPendingIntent()设置对象的点击行为 - 例如使按钮启动一个Activity。但是,这种方法不适用于单个收集项目中的子视图(为了阐明,您可以使用setOnClickPendingIntent()在启动应用程序的Gmail应用程序小部件中设置全局按钮,但不是在单独的列表项目中)。相反,要将点击行为添加到集合中的单个项目,请使用setOnClickFillInIntent()。这需要为您的收藏视图设置待定的意图模板,然后通过您的收藏夹设置收藏集中每个项目的填充意图RemoteViewsFactory。

本节使用StackView Widget示例来描述如何将行为添加到单个项目。在StackView Widget示例中,如果用户触摸顶部视图,应用小部件将显示Toast消息“触摸视图n ”,其中 n是触摸视图的索引(位置)。这是如何工作的:

  • 该StackWidgetProvider(一个AppWidgetProvider子类)创建待意图已调用自定义操作TOAST_ACTION。
  • 当用户触摸视图时,意图被触发并广播 TOAST_ACTION。
  • 这个广播由所截取StackWidgetProvider的 onReceive()方法,以及应用程序插件播放 Toast所触摸的视图的信息。收集项目的数据是由RemoteViewsFactory,通过RemoteViewsService。提供的。

:该StackView的Widget样品使用广播,但通常一个应用程序窗口小部件只会在这样一个情况下推出的活动。

设置未决的意向模板

该StackWidgetProvider(AppWidgetProvider子)建立了一个悬而未决的意图。集合中的个人物品不能建立自己的未决意图。相反,整个集合设置了一个未决的意图模板,并且各个项目设置了一个填充意图,以逐个项目为基础创建独特的行为。

该类还接收用户触摸视图时发送的广播。它在它的onReceive()方法中处理这个事件。如果意图的动作是 TOAST_ACTION,则应用小部件显示Toast 当前视图的消息。

public class StackWidgetProvider extends AppWidgetProvider {
    public static final String TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION";
    public static final String EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM";

    ...

    // Called when the BroadcastReceiver receives an Intent broadcast.
    // Checks to see whether the intent's action is TOAST_ACTION. If it is, the app widget
    // displays a Toast message for the current item.
    @Override
    public void onReceive(Context context, Intent intent) {
        AppWidgetManager mgr = AppWidgetManager.getInstance(context);
        if (intent.getAction().equals(TOAST_ACTION)) {
            int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
            int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
            Toast.makeText(context, "Touched view " + viewIndex, Toast.LENGTH_SHORT).show();
        }
        super.onReceive(context, intent);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // update each of the app widgets with the remote adapter

            // Sets up the intent that points to the StackViewService that will
            // provide the views for this collection.
            Intent intent = new Intent(context, StackWidgetService.class);
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
            // When intents are compared, the extras are ignored, so we need to embed the extras
            // into the data so that the extras will not be ignored.
            intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
            RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
            rv.setRemoteAdapter(appWidgetIds[i], R.id.stack_view, intent);

            // The empty view is displayed when the collection has no items. It should be a sibling
            // of the collection view.
            rv.setEmptyView(R.id.stack_view, R.id.empty_view);

            // This section makes it possible for items to have individualized behavior.
            // It does this by setting up a pending intent template. Individuals items of a collection
            // cannot set up their own pending intents. Instead, the collection as a whole sets
            // up a pending intent template, and the individual items set a fillInIntent
            // to create unique behavior on an item-by-item basis.
            Intent toastIntent = new Intent(context, StackWidgetProvider.class);
            // Set the action for the intent.
            // When the user touches a particular view, it will have the effect of
            // broadcasting TOAST_ACTION.
            toastIntent.setAction(StackWidgetProvider.TOAST_ACTION);
            toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
            intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
            PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
            rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent);

            appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
        }
    super.onUpdate(context, appWidgetManager, appWidgetIds);
    }
}

设置填充意图

您RemoteViewsFactory必须为集合中的每个项目设置填充意图。这使得区分给定物品的单个点击动作成为可能。然后将填充意图与PendingIntent模板组合以确定点击该项目时将执行的最终意图。

public class StackWidgetService extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
    }
}

class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
    private static final int mCount = 10;
    private List<WidgetItem> mWidgetItems = new ArrayList<WidgetItem>();
    private Context mContext;
    private int mAppWidgetId;

    public StackRemoteViewsFactory(Context context, Intent intent) {
        mContext = context;
        mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
    }

    // Initialize the data set.
        public void onCreate() {
            // In onCreate() you set up any connections / cursors to your data source. Heavy lifting,
            // for example downloading or creating content etc, should be deferred to onDataSetChanged()
            // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
            //
                mWidgetItems.add(new WidgetItem(i + "!"));
            }
           ...
        }
        ...

        // Given the position (index) of a WidgetItem in the array, use the item's text value in
        // combination with the app widget item XML file to construct a RemoteViews object.
        public RemoteViews getViewAt(int position) {
            // position will always range from 0 to getCount() - 1.

            // Construct a RemoteViews item based on the app widget item XML file, and set the
            // text based on the position.
            RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);
            rv.setTextViewText(R.id.widget_item, mWidgetItems.get(position).text);

            // Next, set a fill-intent, which will be used to fill in the pending intent template
            // that is set on the collection view in StackWidgetProvider.
            Bundle extras = new Bundle();
            extras.putInt(StackWidgetProvider.EXTRA_ITEM, position);
            Intent fillInIntent = new Intent();
            fillInIntent.putExtras(extras);
            // Make it possible to distinguish the individual on-click
            // action of a given item
            rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);

            ...

            // Return the RemoteViews object.
            return rv;
        }
    ...
    }

保持收集数据新鲜

下图说明了发生更新时使用集合的应用程序小部件中发生的流程。它显示了应用程序小部件代码如何与之交互 RemoteViewsFactory以及如何触发更新:
【Android必备】构建一个App小部件(24)【代码块异常】

使用集合的应用程序小部件的一个功能是能够为用户提供最新的内容。例如,考虑Android 3.0 Gmail应用程序小部件,它为用户提供其收件箱的快照。为了实现这一点,您需要能够触发您的RemoteViewsFactory和收藏视图来获取并显示新数据。你AppWidgetManager打电话来实现这一点notifyAppWidgetViewDataChanged()。此调用会导致回调您 RemoteViewsFactory的onDataSetChanged()方法,从而使您有机会获取任何新数据。请注意,您可以在onDataSetChanged()回调中同步执行处理密集型操作 。您保证在调用元数据或视图数据之前完成此调用RemoteViewsFactory。另外,您可以在其中执行处理密集型操作getViewAt() 方法。如果该呼叫需要很长的时间,装载视图(由指定 RemoteViewsFactory的 getLoadingView()方法)将被显示在集合视图,直到它返回相应的位置。

Lastest Update:2018.04.27

联系我

QQ:94297366
微信打赏:https://pan.baidu.com/s/1dSBXk3eFZu3mAMkw3xu9KQ

公众号推荐:

【Android必备】构建一个App小部件(24)【代码块异常】

猜你喜欢

转载自blog.51cto.com/4789781/2122487