App Widgets 是指能够嵌入其他应用程序中的小组件,并且能够周期性地进行更新。
App Widgets 并不是 Android 应用程序的核心组件,但却是应用程序开发不可或缺的部分。我们可以通过 App Widgets 使我们的 UI 界面更多样化,也可以通过 App Widget Provider 发布我们自己开发的 App Widgets 组件。
一个能够用于容纳 App Widgets 组件的应用程序组件被称为 App Widgets Host(App Widgets 宿主),例如图 1 所示的音乐播放程序。
图 1 App Widgets Host
Android 7.0 中涉及部分 App Widgets 类的使用方法会在后面教程进行详细介绍,本节主要对使用 App Widget Provider 发布自己的 App Widget 组件的方法进行简单介绍。
为了创建一个自己的 App Widget,需要完成以下工作。
定义在 XML 文件中的用于描述 App Widget 的元数据对象,比如 App Widget 的布局、更新频率以及相关的 AppWidgetProvider 类。
在 AppWidgetProvider 类中定义了一系列方法,这些方法允许开发者以编程的方式和自己的 App Widget 进行交互,这种交互基于广播事件。当 App Widget 的状态发生改变,例如更新、启用、禁用和删除的时候,你都会接收到相应的广播通知。
在 XML 文件中为 App Widget 定义初始布局。
这是一个可选的 Activity,当用户添加 App Widget 时该 Activity 会被启动,并允许用户在创建 App Widget 时修改相关设置。
下面进行详细介绍。
首先,在 AndroidManifest.xml 文件中对 AppWidgetProvider 类进行声明。
<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 属性,它指定了 App Widget 使用的 AppWidgetProvider 的名字。
<intent-filter> 元素必须包括一个含有 android:name 属性的 <action> 元素。该元素指定 AppWidgetProvider 接受 ACTION_APPWIDGET_UPDATE 广播。这是唯一一个必须被显式声明的广播。当有必要的时候,AppWidgetManager 会自动发送所有其他App Widget 广播给 AppWidgetProvider。
<meta-data> 元素指定了 AppWidgetProviderInfo 资源并需要以下属性。
AppWidgetProviderInfo 用于定义 App Widget 的一系列基本特性,例如最小布局的尺寸、初始的布局资源、刷新频率以及创建时要加载的配置 Activity 等。
使用 <appwidget-provider> 元素标签在 XML 中定义 AppWidgetProviderInfo 对象并保存到项目的 res/xml/ 目录下,例如:
<appwidget-provider xmlns:android = "http://schemas.android.com/apk/res/android"
android:minWidth = "294dip"
android:minHeight = "72dip"
android:updatePeriodMillis = "86400000"
android:initialLayout = "@layout/example_appwidget"
android:configure = "com.example.android.ExampleAppWidgetConfigure"
>
</appwidget-provider>
其中:
必须在 res/layout 目录下以 XML 文件的方式为 App Widget 定义一个布局文件。
App Widget 的布局是基于 RemoteViews 对象的,而 RemoteViews 对象可以支持以下布局:
和以下的小组件类:
但是并不支持它们的派生类。
此外,RemoteView 还支持 ViewStub,该组件不可见,自身无尺寸,可用于对布局资源进行支撑。
如果没有为自定义的 Widget 定义边界,它就会自动扩展到屏幕大小。因此,我们需要为自定义的 App Widget 定义边界。
自 Android 4.0 开始,App Widget 会自动在 Widget 的边界环绕盒之间添加空隙,以便为 Widget 和其他小组件以及屏幕上的图标提供更好的排列组合方式。为实现这个行为,我们需要将应用程序中的 "targetSdkVersion" 属性设置为大于 14。
实际上,我们可以自己定义一个带有自定义边界的布局,并且使该布局在应用于早期平台版本时正常显示边界,而在 Android 4.0 以后版本的平台上不显示额外边界。定义过程如下:
1) 设置 targetSdkVersion 为大于 14 的值。
2) 创建一个布局,并为其设置 dimension 资源,其边界信息由 dimension 资源设定,代码如下:
<FrameLayout
android:layout_width = "match_parent"
android:layout_height = "match_parent"
android:padding = "@dimen/widget_margin">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
...
</LinearLayout>
</FrameLayout>
3) 创建两个 dimension 资源,一个在 res/values/ 目录下,用于提供低于 Android 4.0 版本的系统的边界信息,另一个在 res/values-v14 下,用于提供高于 Android 4.0 版本的操作系统的边界信息。
例如,res/values/dimens.xml 定义如下:
而 res/values-v14/dimens.xml 定义如下:
首先,AppWidgetProvider 类是 BroadcastReceiver 类的子类,可以方便地处理 App Widget 发出的广播,因此,其必须被声明在清单文件中的 <receiver> 元素中。
AppWidgetProvider 只接受和相应的 App Widget 相关的广播消息,例如这个 App Widget 被更新、被删除、被启用或者被禁用的时候。当这些广播事件发生的时候,AppWidgetProvider 会接收到以下方法的调用请求。
每间隔一定时间该方法就会被调用。
用于对 App Widget 进行更新。间隔时间由 AppWidgetProviderInfo 元数据中的 updatePeriodMillis 属性指定。当用户添加 App Widget 时,该方法也会被调用。因此,该方法中应该执行必要的操作,例如为视图定义事件处理器或者启动一个临时的服务等。
如果你为 App Widget 定义了配置 Activity,就应该由配置 Activity 负责进行第一次更新,而 onUpdate() 方法不会在用户执行添加操作的时候被调用,而只会在后期的更新时被调用。
该方法在 Widget 被首次放置到应用程序中或者 Widget 的尺寸被更改时被调用。
该方法在 App Widget 被从 App Widget 宿主中删除的时候被调用。
该方法在 App Widget 的第一个实例被创建时被调用。
若用户添加了两个 App Widget 的实例,则该方法只会在第一次添加时被调用。如果你需要打开数据库或者其他只需要进行一次的设置,那么将代码放在这个方法中是个不错的主意。
该方法在最后一个 App Widget 实例从 App Widget 宿主中被删除的时候调用。
在该方法中,你应该对在 onEnabled() 方法中的操作进行善后,例如删除一个临时的数据库。
每当接收到一个广播,该方法都会被调用。
并且,该方法会在上述各个方法之前被调用。通常我们不需要重写该方法,因为默认的 AppWidgetProvider 类已经很好地实现了对所有广播的过滤和处理方法的调用。
可见 onUpdate() 方法是最重要的回调方法,如果你创建的 App Widget 不需要进行创建临时文件等操作,那么你可能只需要定义 onUpdate() 方法就可以了。
例如,当你创建了一个带有 Button 的 App Widget,当点击按钮时会启动一个 Activity,那么你的 AppWidgetProvider 类应该像下面这样定义:
public class ExampleAppWidgetProvider extends AppWidgetProvider {
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetlds) {
final int N = appWidgetlds.length;
// Perform this loop procedure for each App Widget that belongs to this provider
for (int i=0; i<N; i++) {
int appWidgetld = appWidgetlds[i];
// Create an Intent to launch ExampleActivity
Intent intent = new Intent(context, ExampleActivity.class);
Pendinglntent pendinglntent = Pendinglntent.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.setOnClickPendinglntent(R.id.button, pendinglntent);
// Tell the AppWidgetManager to perform an update on the current app widget appWidgetManager.updateAppWidget(appWidgetld, views);
}
}
}
其中,appWidgetIds 是一个存放 ID 的数组,其中的每一个 ID 值都标识一个 AppWidgetProvider 创建的 App Widget。如果该数组中存放了多个 App Widget 的 ID,那么这些 App Widget 会被同步更新。
如果你想直接用自己的类接收并处理 App Widget 的广播,那么你需要实现自己的 BroadcastReceiver,重写 onReceiver() 方法,并处理以下 4 个 Intent:
如果想让用户在添加新的 App Widget 的时候对颜色、尺寸、更新周期等属性进行配置,那么就需要创建一个配置 Activity。配置 Activity 会在 App Widget 被创建时由其宿主启动。
该配置 Activity 需要在 Manifest 文件中进行声明,通过 ACTION_APPWIDGET_CONFIGURE 活动被宿主启动,代码如下:
<activity android:name = ".ExampleAppWidgetConfigure">
<intent-filter>
<action android:name = "android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
此外,该 Activity 还需要在 AppWidgetProviderInfo XML 中通过 android:configure 属性被声明,例如:
<appwidget-provider xmlns:android = "http://schemas.android.com/apk/res/android"
...
android:configure = "com.example.android.ExampleAppWidgetConfigure"
... >
</appwidget-provider>
当为 App Widget 定义了配置 Activity 后,Widget 在被创建时不会再调用 onUpdate 方法。
使用配置 Activity 对 App Widget 进行更新
当 Widget 使用了配置 Activity 后,配置 Activity 会在用户完成设置后对 Widget 进行更新。通过配置 Activity 对 Widget 进行更新并关闭配置 Activity 的过程如下:
1) 从启动 Activity 的 Intent 中获取 App Widget 的 ID 值。
Intent intent = getlntent();
Bundle extras = intent.getExtras();
if (extras != null) {
mAppWidgetld = extras.getlnt(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
}
2) 执行 App Widget 配置。
3) 完成配置后,获取 AppWidgetManager 类的实例。
4) 通过 RomoteViews 布局对 App Widget 进行更新。
5) 创建返回 Intent,设置 Activity 返回值,并关闭 Activity。
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,mAppWidgetId);
setResult(RESULT_OK,resultValue);
finish();