Android 窗口小部件—App Widget

1.App Widget简介

App Widget是应用程序窗口小部件(Widget)是微型的应用程序视图,它可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。你可以通过一个App Widget Provider来发布一个Widget。

image

2.App Widget主要的相关类介绍

2.1 AppWidgetProvider

AppWidgetProvider 继承自 BroadcastReceiver,它能接收 widget 相关的广播,例如 widget 的更新、删除、开启和禁用等。

AppWidgetProvider中的广播处理函数如下:

onUpdate()
当 widget 更新时被执行。同样,当用户首次添加 widget 时,onUpdate() 也会被调用,这样 widget 就能进行必要的设置工作(如果需要的话) 。但是,如果定义了 widget 的 configure属性(即android:config,后面会介绍),那么当用户首次添加 widget 时,onUpdate()不会被调用;之后更新 widget 时,onUpdate才会被调用。

onAppWidgetOptionsChanged()
当 widget 被初次添加 或者 当 widget 的大小被改变时,执行onAppWidgetOptionsChanged()。你可以在该函数中,根据 widget 的大小来显示/隐藏某些内容。可以通过 getAppWidgetOptions() 来返回 Bundle 对象以读取 widget 的大小信息,Bundle中包括以下信息:
OPTION_APPWIDGET_MIN_WIDTH — 包含 widget 当前宽度的下限,以dp为单位。
OPTION_APPWIDGET_MIN_HEIGHT — 包含 widget 当前高度的下限,以dp为单位。
OPTION_APPWIDGET_MAX_WIDTH — 包含 widget 当前宽度的上限,以dp为单位。
OPTION_APPWIDGET_MAX_HEIGHT — 包含 widget 当前高度的上限,以dp为单位。

onAppWidgetOptionsChanged() 是 Android 4.1 引入的。

onDeleted(Context, int[])
当 widget 被删除时被触发。

onEnabled(Context)
当第1个 widget 的实例被创建时触发。也就是说,如果用户对同一个 widget 增加了两次(两个实例),那么onEnabled()只会在第一次增加widget时触发。

onDisabled(Context)
当最后1个 widget 的实例被删除时触发。

onReceive(Context, Intent)
接收到任意广播时触发,并且会在上述的方法之前被调用。

总结,AppWidgetProvider 继承于 BroadcastReceiver。实际上,App Widge中的onUpdate()、onEnabled()、onDisabled()等方法都是在 onReceive()中调用的;是onReceive()对特定事情的响应函数。

2.2 AppWidgetProviderInfo

AppWidgetProviderInfo描述一个App Widget元数据,比如App Widget的布局,更新频率,以及AppWidgetProvider 类。这个应该在XML里定义。下面以XML示例来对AppWidgetProviderInfo中常用的类型进行说明。

1
2
3
4
5
6
7
8
9
10
11
<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|keyguard"
  android:initialKeyguardLayout="@layout/example_keyguard">
</appwidget-provider>

示例说明

minWidth 和minHeight

它们指定了App Widget布局需要的最小区域。缺省的App Widgets所在窗口的桌面位置基于有确定高度和宽度的单元网格中。如果App Widget的最小长度或宽度和这些网格单元的尺寸不匹配,那么这个App Widget将上舍入(上舍入即取比该值大的最接近的整数——译者注)到最接近的单元尺寸。

minResizeWidth 和 minResizeHeight

它们属性指定了 widget 的最小绝对尺寸。也就是说,如果 widget 小于该尺寸,便会因为变得模糊、看不清或不可用。 使用这两个属性,可以允许用户重新调整 widget 的大小,使 widget 的大小可以小于 minWidth 和 minHeight。
注意:(01) 当 minResizeWidth 的值比 minWidth 大时,minResizeWidth 无效;当 resizeMode 的取值不包括 horizontal 时,minResizeWidth 无效。

(02) 当 minResizeHeight 的值比 minHeight 大时,minResizeHeight 无效;当 resizeMode 的取值不包括 vertical 时,minResizeHeight 无效。

updatePeriodMillis
它定义了 widget 的更新频率。实际的更新时机不一定是精确的按照这个时间发生的。建议更新尽量不要太频繁,最好是低于1小时一次。 或者可以在配置 Activity 里面供用户对更新频率进行配置。 实际上,当updatePeriodMillis的值小于30分钟时,系统会自动将更新频率设为30分钟!关于这部分,后面会详细介绍。
注意: 当更新时机到达时,如果设备正在休眠,那么设备将会被唤醒以执行更新。如果更新频率不超过1小时一次,那么对电池寿命应该不会造成多大的影响。 如果你需要比较频繁的更新,或者你不希望在设备休眠的时候执行更新,那么可以使用基于 alarm 的更新来替代 widget 自身的刷新机制。将 alarm 类型设置为 ELAPSED_REALTIME 或 RTC,将不会唤醒休眠的设备,同时请将 updatePeriodMillis 设为 0。

initialLayout
指向 widget 的布局资源文件

configure
可选属性,定义了 widget 的配置 Activity。如果定义了该项,那么当 widget 创建时,会自动启动该 Activity。

previewImage
指定预览图,该预览图在用户选择 widget 时出现,如果没有提供,则会显示应用的图标。该字段对应在 AndroidManifest.xml 中 receiver 的 android:previewImage 字段。由 Android 3.0 引入。

autoAdvanceViewId
指定一个子view ID,表明该子 view 会自动更新。在 Android 3.0 中引入。

resizeMode
指定了 widget 的调整尺寸的规则。可取的值有: “horizontal”, “vertical”, “none”。”horizontal”意味着widget可以水平拉伸,“vertical”意味着widget可以竖值拉伸,“none”意味着widget不能拉伸;默认值是”none”。Android 3.1 引入。

widgetCategory
指定了 widget 能显示的地方:能否显示在 home Screen 或 lock screen 或 两者都可以。它的取值包括:”home_screen” 和 “keyguard”。Android 4.2 引入。

initialKeyguardLayout
指向 widget 位于 lockscreen 中的布局资源文件。Android 4.2 引入。

下面我们来看一个简单的例子:

xml配置文件:

1
2
3
4
5
6
7
8
appwidget-provider
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dp"
    android:minHeight="72dp"
    android:updatePeriodMillis="86400000"
    android:initialLayout="@layout/example_appwidget"
 >
</appwidget-provider>

AppWidgetProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package lpq.appwidget01;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;

public class ExampleAppWidgetProvider extends AppWidgetProvider{

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
            int[] appWidgetIds) {
        System.out.println("onupdate");
        super.onUpdate(context, appWidgetManager, appWidgetIds);
    }

    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        System.out.println("onDeleted");
        super.onDeleted(context, appWidgetIds);
    }

    @Override
    public void onDisabled(Context context) {
        System.out.println("onDisabled");
        super.onDisabled(context);
    }

    @Override
    public void onEnabled(Context context) {
        System.out.println("onEnabled");
        super.onEnabled(context);
    }

}

3. PendingIntent

要得到一个pendingIntent对象,使用方法类的静态方法 getActivity(Context, int, Intent, int), getBroadcast(Context, int, Intent, int) ,  getService(Context, int, Intent, int)  分别对应着Intent的3个行为,跳转到一个activity组件、打开一个广播组件和打开一个服务组件。

参数有4个,比较重要的事第三个和第一个,其次是第四个和第二个。可以看到,要得到这个对象,必须传入一个Intent作为参数,必须有context作为参数。

pendingIntent是一种特殊的Intent。主要的区别在于Intent的执行立刻的,而 pendingIntent的执行不是立刻的。pendingIntent执行的操作实质上是参数传进来的Intent的操作,但是使用 pendingIntent的目的在于它所包含的Intent的操作的执行是需要满足某些条件的。

 

4. RemoteViews

RemoteView描述一个view,而这个view是在另外一个进程显示的。它inflate于layout资源文件。并且提供了可以修改过view内容的一些简单基础的操作。

下面我们来看一个RemoteViews通过PendingIntent实现事件处理的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
            int[] appWidgetIds) {
        for (int i = 0; i < appWidgetIds.length; i++) {
            System.out.println(appWidgetIds[i]);
            //创建一个Intent对象
            Intent intent = new Intent(context,TargetActivity.class);
            //创建一个PendingIntent
            PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
            //为按钮绑定事件处理器
            //第一个参数用来指定被绑定处理器的控件的ID
            //第二个参数用来指定当事件发生时,哪个PendingIntent将会被执行
            remoteViews.setOnClickPendingIntent(R.id.widgetButtonId, pendingIntent);
            //更新AppWidget
            //第一个参数用于指定被更新AppWidget的ID
            appWidgetManager.updateAppWidget(appWidgetIds[i], remoteViews);
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds);
    }

5.怎么接受来自AppWidget的广播

5.1 在AndroidMainfest.xml当中为AppWidgetProvider注册新的intent-filter

1
2
3
4
5
6
7
8
9
10
<receiver android:name="ExampleAppWidgetProvider">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <intent-filter>
                <action android:name="lpq.appwidget03.UPDATE_APP_WIDGET"/>
            </intent-filter>
            <meta-data android:name="android.appwidget.provider"
                android:resource="@xml/example_appwidget_info" />
        </receiver>

5.2 使用getBroadcast()方法创建一个PengdingIntent;

1
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

5.3 为AppWidget当中的控件注册处理器;

1
remoteViews.setOnClickPendingIntent(R.id.widgetButtonId, pendingIntent);

5.4 在onReceive方法当中接受广播消息;

1
2
3
4
String action = intent.getAction();
        if (UPDATE_ACTION.equals(action)) {
            System.out.println("onReceive--->" + UPDATE_ACTION);
        }

5.5 完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package lpq.appwidget03;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;

public class ExampleAppWidgetProvider extends AppWidgetProvider {
    //定义一个常量字符串,该常量用于命名Action
    private static final String UPDATE_ACTION = "mars.appwidget03.UPDATE_APP_WIDGET";

    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        // TODO Auto-generated method stub
        super.onDeleted(context, appWidgetIds);
    }

    @Override
    public void onDisabled(Context context) {
        // TODO Auto-generated method stub
        super.onDisabled(context);
    }

    @Override
    public void onEnabled(Context context) {
        // TODO Auto-generated method stub
        super.onEnabled(context);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        String action = intent.getAction();
        if (UPDATE_ACTION.equals(action)) {
            System.out.println("onReceive--->" + UPDATE_ACTION);
        }
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
            int[] appWidgetIds) {
        //创建一个Intent对象
        Intent intent = new Intent();
        //为Intent对象设置Action
        intent.setAction(UPDATE_ACTION);
        //使用getBroadcast方法,得到一个PendingIntent对象,当该对象执行时,会发送一个广播
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
                intent, 0);
        RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
                R.layout.example_appwidget);
        remoteViews.setOnClickPendingIntent(R.id.widgetButtonId, pendingIntent);
        appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
    }

}

6. 使用RemoteViews对象更新AppWidget当中控件的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package lpq.appwidget04;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;

public class ExampleAppWidgetProvider extends AppWidgetProvider {
    private static final String UPDATE_ACTION = "lpq.appwidget03.UPDATE_APP_WIDGET";

    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {

    }

    @Override
    public void onDisabled(Context context) {
    }

    @Override
    public void onEnabled(Context context) {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (UPDATE_ACTION.equals(action)) {
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
                    R.layout.example_appwidget);
            remoteViews.setImageViewResource(R.id.imageId, R.drawable.ku);
            remoteViews.setTextViewText(R.id.widgetTextId, "test");
            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
            ComponentName componentName = new ComponentName(context,ExampleAppWidgetProvider.class);
            appWidgetManager.updateAppWidget(componentName, remoteViews);
        } else {
            super.onReceive(context, intent);
        }
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
            int[] appWidgetIds) {
        Intent intent = new Intent();
        intent.setAction(UPDATE_ACTION);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, -1,
                intent, 0);
        RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
                R.layout.example_appwidget);
        remoteViews.setOnClickPendingIntent(R.id.widgetButtonId, pendingIntent);
        appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
    }

}

除非注明,饮水思源博客文章均为原创,转载请以链接形式标明本文地址

本文地址:http://www.alonemonkey.com/android-appwidget.html

本文链接:http://www.alonemonkey.com/android-appwidget.html