Android 5从入门到精通
上QQ阅读APP看书,第一时间看更新

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 App Widgets Host

Android5中涉及的部分App Widgets类的使用方法会在第四章进行详细介绍,本部分主要对如何使用App Widget Provider发布自己的App Widget组件的方法进行简单介绍。

3.5.1 基础知识

为了创建一个自己的App Widget,需要完成以下工作:


●AppWidgetProviderInfo元数据


定义在XML文件中的用于描述App Widget的元数据对象,比如App Widget的布局,更新频率,以及相关的AppWidgetProvider类。


●实现AppWidgetProvider类


在AppWidgetProvider类中定义了一系列方法,这些方法允许开发者以编程的方式和自己的App Widget进行交互,这种交互基于广播事件。当App Widget的状态发生改变,例如更新、启用、禁用和删除的时候,你都会接收到相应的广播通知。


●视图布局


在XML文件中为App Widget定义初始布局。


●实现App Widget配置Activity


这是一个可选的Activity,当用户添加App Widget时该Activtity会被启动,并允许用户在创建App Widget时修改相关设置。

下面进行详细介绍。

3.5.2 在Manifest文件中声明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资源并需要以下属性:


●android:name–指定元数据名称。

●android:resource–指定AppWidgetProviderInfo资源路径。

3.5.3 增加AppWidgetProviderInfo元数据

AppWidgetProviderInfo用于定义App Widget的一系列基本特性,例如最小布局的尺寸,初始的布局资源、刷新频率以及创建时要加载的配置Activity等。使用<appwidget-provider>元素标签在XML中定义AppWidgetProviderInfo对象并保存到项目的res/xml/目录下。例如:

    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:minWidth="294dp" <!-- density-independent pixels -->
        android:minHeight="72dp"
        android:updatePeriodMillis="86400000" <!-- once per day -->
        android:initialLayout="@layout/example_appwidget"
        android:configure="com.example.android.ExampleAppWidgetConfigure" >
    </appwidget-provider>

其中:


●minWidth 和minHeight属性的值指定了这个App Widget布局需要的最小区域。

●updatePerdiodMillis属性定义了App Widget框架调用onUpdate()方法来从AppWidgetProvider请求一次更新的频率。实际上更新的时间并不精准。建议更新频率越低越好,比如一小时更新一次,这样可以节省电力,或者根据用户的配置调整更新频率,比如有个人每15分钟想查看一下股票的报价,这样可以将频率设置为一小时更新四次。

●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定义边界。

自Android4.0开始,App Widget会自动在Widget的边界环绕盒之间添加空隙,以便为Widget和其他小组件以及屏幕上的图标提供更好的排列组合方式。为实现这一个行为,我们需要将应用程序中的“targetSdkVersion”属性设置为大于14。

实际上,我们可以自己定义一个带有自定义边界的布局,并且使该布局在应用于早期平台版本时正常显示边界,而在Android4.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="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal"
        android:background="@drawable/my_widget_background">
        …
      </LinearLayout>



    </FrameLayout>

(3)创建两个dimension资源,一个在res/values/目录下,用于提供早于Android4.0版本的系统的边界信息,另外一个在res/values-v14下,用于提供高于Android4.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类应该像下面这样定义:

        public class ExampleAppWidgetProvider extends AppWidgetProvider {



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



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

其中appWidgetIds是一个存放ID的数组,其中的每一个ID值都标识一个AppWidgetProvider创建的App Widget。如果该数组中存放了多个App Widget的ID,那么这些App Widget会被同步更新。

3.5.7 接收App Widget的广播

如果你想直接用自己的类接收并处理App Widget的广播,那么你需要实现自己的BroadcastReceiver,重写onReceiver()方法,并处理以下四个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 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方法。

3.5.9 使用配置Activity对App Widget进行更新

当Widget使用了配置Activity后,配置Activity会在用户完成设置后对Widget进行更新。通过配置Activity对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)完成配置后,获取AppWidgetManager类的实例

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

(4)通过RomoteViews布局对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();