下面通过我所写的一个课表应用来说明如何使用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); } }