3.5 App Widgets
App Widgets是指能够嵌入其他应用程序中的小组件,并且能够周期性地进行更新。App Widgets并不是Android应用程序的核心组件,但却是应用程序开发不可或缺的部分。我们可以通过App Widgets使我们的UI界面更多样化,也可以通过App Widget Provider发布我们自己开发的App Widgets组件。一个能够用于容纳App Widgets组件的应用程序组件被称为App Widgets Host(App Widgets宿主),例如图3.3所示的音乐播放程序。
图3.3 App Widgets Host
Android 7.0中涉及部分App Widgets类的使用方法会在第4章进行详细介绍,本节主要对使用App Widget Provider发布自己的App Widget组件的方法进行简单介绍。
3.5.1 基础知识
为了创建一个自己的App Widget,需要完成以下工作。
1.AppWidgetProviderInfo元数据
定义在XML文件中的用于描述App Widget的元数据对象,比如App Widget的布局、更新频率以及相关的AppWidgetProvider类。
2.实现AppWidgetProvider类
在AppWidgetProvider类中定义了一系列方法,这些方法允许开发者以编程的方式和自己的App Widget进行交互,这种交互基于广播事件。当App Widget的状态发生改变,例如更新、启用、禁用和删除的时候,你都会接收到相应的广播通知。
3.视图布局
在XML文件中为App Widget定义初始布局。
4.实现App Widget配置Activity
这是一个可选的Activity,当用户添加App Widget时该Activity会被启动,并允许用户在创建App Widget时修改相关设置。
下面进行详细介绍。
3.5.2 在Manifest文件中声明App Widget
首先,在AndroidManifest.xml文件中对AppWidgetProvider类进行声明。相关代码如下:
<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:resource:指定AppWidgetProviderInfo资源路径。
3.5.3 增加AppWidgetProviderInfo元数据
AppWidgetProviderInfo用于定义App Widget的一系列基本特性,例如最小布局的尺寸、初始的布局资源、刷新频率以及创建时要加载的配置Activity等。使用<appwidget-provider>元素标签在XML中定义AppWidgetProviderInfo对象并保存到项目的res/xml/目录下,例如:
其中:
• minWidth和minHeight属性的值指定了这个App Widget布局需要的最小区域。
• updatePerdiodMillis属性定义了App Widget框架调用onUpdate()方法来从AppWidgetProvider请求一次更新的频率。实际上更新的时间并不精准。建议更新频率越低越好,比如一小时更新一次,这样可以节省电力,或者根据用户的配置调整更新频率,比如有个人每15分钟想查看一下股票的报价,这样可以将频率设置为一小时更新4次。
• initialLayout属性指向App Widget使用的布局的资源。
• configure属性定义了该App Widget被加载时使用的配置Activity。
3.5.4 创建App Widget布局
必须在res/layout目录下以XML文件的方式为App Widget定义一个布局文件。App Widget的布局是基于RemoteViews对象的,而RemoteViews对象可以支持以下布局:
• Framelayout
• LinearLayout
• RelativeLayout
• GridLayout
和以下的小组件类:
• AnalogClock
• Button
• Chronometer
• ImageButton
• ImageView
• ProgressBar
• TextView
• ViewFlipper
• ListView
• GridView
• StackView
• AdapterViewFlipper
但是并不支持它们的派生类。
此外,RemoteView还支持ViewStub,该组件不可见,自身无尺寸,可用于对布局资源进行支撑。
3.5.5 为App Widget添加边界
如果没有为自定义的Widget定义边界,它就会自动扩展到屏幕大小。因此,我们需要为自定义的App Widget定义边界。
自Android 4.0开始,App Widget会自动在Widget的边界环绕盒之间添加空隙,以便为Widget和其他小组件以及屏幕上的图标提供更好的排列组合方式。为实现这个行为,我们需要将应用程序中的“ targetSdkVersion”属性设置为大于14。
实际上,我们可以自己定义一个带有自定义边界的布局,并且使该布局在应用于早期平台版本时正常显示边界,而在Android 4.0以后版本的平台上不显示额外边界。定义过程如下:
步骤01 设置targetSdkVersion为大于14的值。
步骤02 创建一个布局,并为其设置dimension资源,其边界信息由dimension资源设定,代码如下:
步骤03 创建两个dimension资源,一个在res/values/目录下,用于提供低于Android 4.0版本的系统的边界信息,另一个在res/values-v14下,用于提供高于Android 4.0版本的操作系统的边界信息。
例如,res/values/dimens.xml定义如下:
<dimen name="widget_margin">8dp</dimen>
而res/values-v14/dimens.xml定义如下:
<dimen name="widget_margin">0dp</dimen>
3.5.6 使用AppWidgetProvider类
首先,AppWidgetProvider类是BroadcastReceiver类的子类,可以方便地处理App Widget发出的广播,因此,其必须被声明在清单文件中的<receiver>元素中。AppWidgetProvider只接受和相应的App Widget相关的广播消息,例如这个App Widget被更新、被删除、被启用或者被禁用的时候。当这些广播事件发生的时候,AppWidgetProvider会接收到以下方法的调用请求。
• onUpdate():每间隔一定时间该方法就会被调用用于对App Widget进行更新。间隔时间由AppWidgetProviderInfo元数据中的updatePeriodMillis属性指定。当用户添加App Widget时,该方法也会被调用。因此,该方法中应该执行必要的操作,例如为视图定义事件处理器或者启动一个临时的服务等。如果你为App Widget定义了配置Activity,就应该由配置Activity负责进行第一次更新,而onUpdate()方法不会在用户执行添加操作的时候被调用,而只会在后期的更新时被调用。
• onAppWidgetOptionsChanged():该方法在Widget被首次放置到应用程序中或者Widget的尺寸被更改时被调用。
• onDeleted(Context, int[]):该方法在App Widget被从App Widget宿主中删除的时候被调用。
• onEnabled(Context):该方法在App Widget的第一个实例被创建时被调用。若用户添加了两个App Widget的实例,则该方法只会在第一次添加时被调用。如果你需要打开数据库或者其他只需要进行一次的设置,那么将代码放在这个方法中是个不错的主意。
• onDisabled(Context):该方法在最后一个App Widget实例从App Widget宿主中被删除的时候调用。在该方法中,你应该对在onEnabled()方法中的操作进行善后,例如删除一个临时的数据库。
• onReceive(Context, Intent):每当接收到一个广播,该方法都会被调用。并且,该方法会在上述各个方法之前被调用。通常我们不需要重写该方法,因为默认的AppWidgetProvider类已经很好地实现了对所有广播的过滤和处理方法的调用。
可见onUpdate()方法是最重要的回调方法,如果你创建的App Widget不需要进行创建临时文件等操作,那么你可能只需要定义onUpdate()方法就可以了。例如,当你创建了一个带有Button的App Widget,当点击按钮时会启动一个Activity,那么你的AppWidgetProvider类应该像下面这样定义:
其中,appWidgetIds是一个存放ID的数组,其中的每一个ID值都标识一个AppWidgetProvider创建的App Widget。如果该数组中存放了多个App Widget的ID,那么这些App Widget会被同步更新。
3.5.7 接收App Widget的广播
如果你想直接用自己的类接收并处理App Widget的广播,那么你需要实现自己的BroadcastReceiver,重写onReceiver()方法,并处理以下4个Intent:
• ACTION_APPWIDGET_UPDATE
• ACTION_APPWIDGET_DELETED
• ACTION_APPWIDGET_ENABLED
• ACTION_APPWIDGET_DISABLED
3.5.8 创建App Widget的配置Activity
如果想让用户在添加新的App Widget的时候对颜色、尺寸、更新周期等属性进行配置,那么就需要创建一个配置Activity。配置Activity会在App Widget被创建时由其宿主启动。
该配置Activity需要在Manifest文件中进行声明,通过ACTION_APPWIDGET_CONFIGURE活动被宿主启动,代码如下:
此外,该Activity还需要在AppWidgetProviderInfo XML中通过android:configure属性被声明,例如:
当为App Widget定义了配置Activity后,Widget在被创建时不会再调用onUpdate方法。
3.5.9 使用配置Activity对App Widget进行更新
当Widget使用了配置Activity后,配置Activity会在用户完成设置后对Widget进行更新。通过配置Activity对Widget进行更新并关闭配置Activity的过程如下:
步骤01 从启动Activity的Intent中获取App Widget的ID值。
步骤02 执行App Widget配置。
步骤03 完成配置后,获取AppWidgetManager类的实例。
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
步骤04 通过RomoteViews布局对App Widget进行更新。
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.example_appwidget); appWidgetManager.updateAppWidget(mAppWidgetId, views);
步骤05 创建返回Intent,设置Activity返回值,并关闭Activity。
Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); setResult(RESULT_OK, resultValue); finish();