桌面小部件AppWidget的使用

在android平台中,显示在HOME界面的一些挂件,即桌面小部件,被称为AppWidget。在自己的程序中适当地加入AppWidget,不但使用户更方便,也能从一定程序上提高本程序的留存率。

下面通过我所写的一个课表应用来说明如何使用AppWidget。
我所写的AppWidget最终结果如下图:



1.首先在res/layout下编写AppWidget的布局文件。
我的代码如下:
appwidget_small.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:background="@drawable/appwidget_bg" >

    <Button
        android:id="@+id/widget_small_refresh"
        android:layout_width="60dp"
        android:layout_height="40dp"
        android:layout_alignParentRight="true"
        android:text="@string/widget_small_refrest"
        android:textAppearance="@android:style/TextAppearance.Medium" />

    <TextView
        android:id="@+id/widget_small_day"
        android:layout_width="60dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        android:layout_below="@id/widget_small_refresh"
        android:gravity="center"
        android:textAppearance="@android:style/TextAppearance.Small" />

    <include
        android:id="@+id/widget_small_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="2dp"
        android:layout_marginLeft="2dp"
        android:layout_marginTop="2dp"
        android:layout_toLeftOf="@id/widget_small_refresh"
        layout="@layout/main_list_item"
        android:background="@drawable/appwidget_list_bg" />

</RelativeLayout>

其中include的是课表信息部分的布局,它在我的MainActivity还用到,这里没有另外编写,直接使用include标签将它引用进来。
main_list_item.xml的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal" >

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/list_item_class"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:singleLine="true"
            android:textAppearance="@android:style/TextAppearance.Medium" />

        <TextView
            android:id="@+id/list_item_time"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:singleLine="true"
            android:textAppearance="@android:style/TextAppearance.Small" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

        <LinearLayout
            android:layout_width="120dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:orientation="vertical" >

            <TextView
                android:id="@+id/list_item_course"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:singleLine="true"
                android:textAppearance="@android:style/TextAppearance.Medium" />

            <TextView
                android:id="@+id/list_item_teacher"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:singleLine="true"
                android:textAppearance="@android:style/TextAppearance.Medium" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="120dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:orientation="vertical" >

            <TextView
                android:id="@+id/list_item_room"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:singleLine="true"
                android:textAppearance="@android:style/TextAppearance.Medium" />

            <TextView
                android:id="@+id/list_item_week"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:singleLine="true"
                android:textAppearance="@android:style/TextAppearance.Medium" />
        </LinearLayout>
    </LinearLayout>

</LinearLayout>

在这里说明一下,对于appWidget的布局文件的根标签如果设置宽高为match_parent(fill_partent),则在HOME界面改变它的大小时它也会自动扩张,否则无论将它占的空间拉伸到多大,它都不会扩张。

2.在/res/xml下编写这个appwidget的信息文件。
widget_small_provider_info.xml代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/appwidget_small"
    android:minHeight="60dp"
    android:minWidth="240dp"
    android:updatePeriodMillis="1800000" >

</appwidget-provider>

上面initialLayout即初始的布局,minHeight和minWidth即最小的宽高,updatePeriodMillis为更新周期。
需要注意的是关于这个更新周期,在我本机G14,android4.0.3上发现它并没有用,百度之后发现它存在着BUG,在有些系统有效,有些则没效。所以如果想在任何机型都能自动更新的话,还要自己写一个service去更新。这个可参考android SDK中的例子。

3.接下来需要编写一个类,继承自AppWidgetProvider。
代码如下:
/*
 * @(#)TableWidgetProvider.java		       Project:UniversityTimetable
 * Date:2013-2-11
 *
 * Copyright (c) 2013 CFuture09, Institute of Software, 
 * Guangdong Ocean University, Zhanjiang, GuangDong, China.
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lurencun.cfuture09.universityTimetable.appwidget;

import java.util.Calendar;

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.database.Cursor;
import android.util.Log;
import android.widget.RemoteViews;

import com.lurencun.cfuture09.universityTimetable.R;

/**
 * @Author Geek_Soledad ([email protected])
 * @Function
 */
public class TableSmallWidgetProvider extends AppWidgetProvider {
	private static final String TAG = "TableSmallWidgetProvider";
	public static final String ACTION_UPDATE = "cfuture09.universityTimetable.action.TIMETABLE.APPWIDGET_SMALL_UPDATE";
	@Override
	public void onDeleted(Context context, int[] appWidgetIds) {
		super.onDeleted(context, appWidgetIds);
		Log.d(TAG, "onDeleted");
	}

	@Override
	public void onDisabled(Context context) {
		super.onDisabled(context);
		Log.d(TAG, "onDisable");
	}

	@Override
	public void onEnabled(Context context) {
		super.onEnabled(context);
		Log.d(TAG, "onEnabled");
	}

	@Override
	public void onReceive(Context context, Intent intent) {
		super.onReceive(context, intent);
		Log.d(TAG, "onReceive");
	}

	@Override
	public void onUpdate(Context context, AppWidgetManager appWidgetManager,
			int[] appWidgetIds) {
		super.onUpdate(context, appWidgetManager, appWidgetIds);
		Log.d(TAG, "update");
	}
}

上面onEnabled在第一次创建AppWidget中执行。
在HOME界面中是可以多次插入同一个AppWidget的,每次插入一个AppWidget,onReceive和onUpdate都会被执行,这一次可以自己去做试验了解它的生命周期,在这里不赘述。

4.在Manifest中声明。
        <receiver
            android:name=".appwidget.TableSmallWidgetProvider"
            android:label="@string/widget_small_4_1" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <intent-filter>
                <action android:name="cfuture09.universityTimetable.action.TIMETABLE.APPWIDGET_SMALL_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_small_provider_info" />
        </receiver>
其中label中引用的字符串即在插入Appwidget时出现的那个名字,如这里为"大学课程表(4*1)",如果不添加,默认为你的程序名,即在application标签中声明的label。然后加入的intent-filter,为其接收的广播,这里还定义了自己的一个action,它将在下面的例子中用到,因为我希望还能手动更新appwidget的数据。

在上面的例子中,一个简单的AppWidget就完成了。

但是我需要的还不够,我还希望这个AppWidget的控件内容是可以改变的,而不是写死在布局文件中的,这就需要用到RemoteViews了。
因为在android中,AppWidget与你的主程序是运行在不同的进程当中的,在这里需要用RemoteViews来进行它们之间的通信,而不是像在Activity中那样方便。
而对于RemoteViews在不同版本的API中,支持的控件(亦其提供的方法)也不同,越往后支持的越多,在2.2中,貌似还不支持AbsListView控件的更新,也只提供了简单的onclick事件绑定的方法。

首先创建一个RemoteViwews对象,RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.appwidget_small);
然后可以通过它的setTextViewText方法设置AppWidget中的TextView的内容,传入的参数为要设置的textview的id和内容。
如果想点击它而打开你的程序的activity,或者更新控件,则也如下面代码所示。
	@Override
	public void onUpdate(Context context, AppWidgetManager appWidgetManager,
			int[] appWidgetIds) {
		super.onUpdate(context, appWidgetManager, appWidgetIds);
		Log.d(TAG, "onUpdate");
		for (int i = 0; i < appWidgetIds.length; i++) {
			RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
					R.layout.appwidget_small);
			// 设置星期几
			remoteViews.setTextViewText(R.id.widget_small_day,
					arrayWeeks[currentDay]);
			// 打开程序
			PendingIntent startIntent = PendingIntent.getActivity(context, 0,
					new Intent(context, MainActivity.class), 0);
			remoteViews.setOnClickPendingIntent(R.id.widget_small_content,
					startIntent);
			// 更新控件的事件绑定
			Intent intent = new Intent();
			intent.setAction(ACTION_UPDATE);
			// 以发送广播消息的方式创建PendingIntent.
			PendingIntent pending_intent = PendingIntent.getBroadcast(context,
					0, intent, 0);
			remoteViews.setOnClickPendingIntent(R.id.widget_small_refresh,
					pending_intent);
			appWidgetManager.updateAppWidget(appWidgetIds[i], remoteViews);
		}
	}

在更新控件中,由于它是发送了一个广播,onReceive将会被执行,但不会执行onUpdate方法,所以这里还需要再修改onReceive方法,否则无法达到更新的目的。
	@Override
	public void onReceive(Context context, Intent intent) {
		Log.d(TAG, "onReceive");
		if (ACTION_UPDATE.equals(intent.getAction())) {
			RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
					R.layout.appwidget_small);
			// 设置星期几
			remoteViews.setTextViewText(R.id.widget_small_day,
					arrayWeeks[currentDay]);
			// 更新节课
			updateWidgetViews(remoteViews, dto);
			PendingIntent startIntent = PendingIntent.getActivity(context, 0,
					new Intent(context, MainActivity.class), 0);
			remoteViews.setOnClickPendingIntent(R.id.widget_small_content,
					startIntent);

			ComponentName componentName = new ComponentName(context,
					TableSmallWidgetProvider.class);
			AppWidgetManager.getInstance(context).updateAppWidget(
					componentName, remoteViews);
		} else {
			super.onReceive(context, intent);
		}
	}

猜你喜欢

转载自maosidiaoxian.iteye.com/blog/1812815