主屏幕部件提供了一种方式在Android主屏幕上呈现经常变化的信息。在手机关机在开机时,信息依然会正常地读取显示。
部件定义至少应包含以下内容:
1、要在主屏幕上显示的布局视图,以及它应该在主屏幕上占用的空间大小。
2、指定更新频率的计时器。
3、名为“部件提供程序”的java类,它可以响应计时器更新,通过向视图填充数据来更改视图。
在内部,Android会为部件实例分配一个ID,以便跟踪它们。此ID被传递到Java回调以及配置器Java类,以便可将更新定向到正确的实例。
部件的声明周期:
1、部件定义阶段。完成此定义需要两个要素:实现AppWidgetProvider的Java类(一个广播接收器)和该部件的布局视图。程序员无法直接访问这些视图以对它们调用方法。只有通过RemoteViews访问这些视图(该类就像一位门卫)。
2、部件实例创建阶段。
我们看看当用户在部件挑选列表中选择部件来创建部件实例时会发生什么。Android调用配置器活动,并要求配置器活动执行以下操作:
(1)启动配置器,调用Intent接收部件实例ID。
(2)通过一组表格字段提示用户收集特定于部件实例的信息。
(3)持久化部件实例信息,以便对部件update的后续调用能访问此信息。
(4)通过检索部件视图布局来准备首次显示部件视图,并为它创建一个RemoteViews对象。
(5)调用RemoteViews对象的方法来设置每个视图对象的值,比如文本、图像等。
(6)再次调用RemoteViews对象在部件的任何子视图上注册任何onClick事件。
(7)告诉AppWidgetManager,使用部件的实例ID在主屏幕上绘制RemoteViews。
(8)返回部件ID并关闭。
3、onUpdate阶段。
在主屏幕上显示了部件实例之后,下一个重要事件就是计时器过期。Android将通过调用onUpdate来响应计时器。onUpdate()是通过广播接收器来调用的。
4、部件视图鼠标单击事件回调阶段。
此操作可以启动一项服务或活动,比如打开浏览器。
5、删除部件实例。
6、卸载部件包。
如果计划卸载包含部件的.apk文件并安装该文件的新版本,则需要清理这些部件。
建议在尝试卸载包之前删除所有部件实例。可以使用android优化大师进行卸载。
示例部件应用程序
1、定义部件提供程序:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.wangle.android.BDayWidget" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="7" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" >
<!--
****************************************************************************************
* Birthday Widget Provider Receiver
****************************************************************************************
--> <receiver android:name=".BDayWidgetProvider"> <meta-data android:name="android.appwidget.provider" android:resource="@xml/bday_appwidget_provider"/> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> </receiver> <!--
****************************************************************************************
* Birthday Provider Configurator Activity
****************************************************************************************
--> <activity android:name=".ConfigureBDayWidgetActivity" android:label="Configure Birthday Widget"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/> </intent-filter> </activity> </application> </manifest>
2、定义部件尺寸
(1)部件视图定义:res/xml/bday_appwidget_provider.xml
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="150dp" android:minHeight="200dp" android:updatePeriodMillis="60000" android:initialLayout="@layout/bday_widget" android:configure="com.wangle.android.BDayWidget.ConfigureBDayWidgetActivity"> </appwidget-provider>
(2)部件视图布局定义:res/layout/bday_widget.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="150dp" android:layout_height="120dp" android:background="@drawable/box1" android:orientation="vertical" > <TextView android:id="@+id/bdw_w_name" android:layout_width="fill_parent" android:layout_height="30dp" android:text="Anonymous" android:background="@drawable/box1" android:gravity="center"/> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="60dp"> <TextView android:id="@+id/bdw_w_days" android:layout_width="wrap_content" android:layout_height="fill_parent" android:text="0" android:gravity="center" android:textSize="30sp" android:layout_weight="50"/> <TextView android:id="@+id/bdw_w_button_buy" android:layout_width="wrap_content" android:layout_height="fill_parent" android:textSize="20sp" android:text="Buy" android:layout_weight="50" android:background="#FF6633" android:gravity="center"/> </LinearLayout> <TextView android:id="@+id/bdw_w_date" android:layout_width="fill_parent" android:layout_height="30dp" android:text="1/1/2000" android:background="@drawable/box1" android:gravity="center"/> </LinearLayout>
3、与部件布局相关的文件:部件背景形状文件
边框形状定义:res/drawable/box1.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" > <stroke android:width="4dp" android:color="#888888"/> <padding android:left="2dp" android:top="2dp" android:right="2dp" android:bottom="2dp"/> <corners android:radius="4dp"/> </shape>
4、实现部件提供程序:
BDAyWidgetProvider.java:
package com.wangle.android.BDayWidget; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; public class BDayWidgetProvider extends AppWidgetProvider { private static final String tag = "BDayWidgetProvider"; public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds){ final int N = appWidgetIds.length; for(int i=0; i<N; i++){ int appWidgetId = appWidgetIds[i]; updateAppWidget(context, appWidgetManager, appWidgetId); } } public void onDeleted(Context context, int[] appWidgetIds){ for(int i=0; i<appWidgetIds.length; i++){ BDayWidgetModel.removePrefs(context, appWidgetIds[i]); } } public void onEnable(Context context){ BDayWidgetModel.clearAllPreferences(context); PackageManager pm = context.getPackageManager(); pm.setComponentEnabledSetting(new ComponentName("com.wangle.android.BDayWidget", ".BDayWidgetProvider"), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); } public void onDisabled(Context context){ BDayWidgetModel.clearAllPreferences(context); PackageManager pm = context.getPackageManager(); pm.setComponentEnabledSetting(new ComponentName("com.wangle.android.BDayWidget", ".BDayWidgetProvider"), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); } private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId){ BDayWidgetModel bwm = BDayWidgetModel.retrieveModel(context, appWidgetId); if(bwm == null){ return; } ConfigureBDayWidgetActivity.updateAppWidget(context, appWidgetManager, bwm); } }
5、实现部件模型:
MVC(模型-视图-控制器)的概念中,模型保存视图需要的数据,视图负责展示,而控制器负责协调视图和模型。
(1)部件模型接口:IWidgetModelSaveContract.java
package com.wangle.android.BDayWidget; import java.util.Map; public interface IWidgetModelSaveContract { public void setValueForPref(String key, String value); public String getPrefname(); public Map<String, String> getPrefsToSave(); public void init(); }
(2)部件模型的抽象实现:APrefWidgetModel.java
我们知道如何在“共享首选项”等持久存储中保存和检索它们自身。以下我们会把数据放到这个文件:/data/data/com.wangle.android.BDayWidget/shared_prefs/com.wangle.android.BDayWidget.BDayWidgetProvider.xml。因为我们这里是使用编程方式来操作首选项的。而我们在使用首选项框架(编写一个活动类来扩展android类android.preference.PreferenceActivity)时,android框架负责持久化首选项,而此时的这个存储数据会被存储到应用程序/data目录下的一个xml文件中,这个xml文件路径为/data/data/[PACKAGE_NAME]/shared_prefs/[PACKAGE_NAME]_preferences.xml
package com.wangle.android.BDayWidget; import java.util.Map; import android.content.Context; import android.content.SharedPreferences; public abstract class APrefWidgetModel implements IWidgetModelSaveContract{ private static String tag = "AWidgetModel"; public int iid; public APrefWidgetModel(int instanceId){ iid = instanceId; } public abstract String getPrefname(); public abstract void init(); public Map<String, String> getPrefsToSave(){return null;} public void savePreferences(Context context){ Map<String, String> keyValuePairs = getPrefsToSave(); if(keyValuePairs == null){ return; }
//保存数据到共享首选项。 SharedPreferences.Editor prefs = context.getSharedPreferences(getPrefname(), 0).edit(); for(String key : keyValuePairs.keySet()){ String value = keyValuePairs.get(key); savePref(prefs, key, value); } prefs.commit(); } private void savePref(SharedPreferences.Editor prefs, String key, String value){ String newKey = getStoredKeyForFieldName(key); prefs.putString(newKey, value); } protected String getStoredKeyForFieldName(String fieldName){ return fieldName + "_" + iid; } public void removePrefs(SharedPreferences.Editor prefs, String key){ String newKey = getStoredKeyForFieldName(key); prefs.remove(newKey); } public static void clearAllPreferences(Context context, String prefName){ SharedPreferences.Editor prefsEdit = context.getSharedPreferences(prefName, 0).edit(); prefsEdit.clear(); prefsEdit.commit(); } public boolean retrievePrefs(Context ctx){ SharedPreferences prefs = ctx.getSharedPreferences(getPrefname(), 0); Map<String, ?> keyValuePairs = prefs.getAll(); boolean prefFound = false; for(String key : keyValuePairs.keySet()){ if(isItMyPref(key) == true){ String value = (String)keyValuePairs.get(key); setValueForPref(key, value); prefFound = true; } } return prefFound; } public void removePrefs(Context context){ Map<String, String> keyValuePairs = getPrefsToSave(); if(keyValuePairs == null){ return; } SharedPreferences.Editor prefs = context.getSharedPreferences(getPrefname(), 0).edit(); for(String key : keyValuePairs.keySet()){ removePrefs(prefs, key); } prefs.commit(); } private boolean isItMyPref(String keyname){ if(keyname.indexOf("_" + iid) > 0){ return true; } return false; } @Override public void setValueForPref(String key, String value) { // TODO Auto-generated method stub } }
(3)生日部件的部件模型实现:BDayWidgetModel.java
package com.wangle.android.BDayWidget; import java.text.ParseException; import java.util.HashMap; import java.util.Map; import android.content.Context; public class BDayWidgetModel extends APrefWidgetModel { private static String tag = "BDayWidgetModel"; private static String BDAY_WIDGET_PROVIDER_NAME = "com.wangle.android.BDayWidget.BDayWidgetProvider"; private String name = "anon"; private static String F_NAME = "name"; private String bday = "1/1/2001"; private static String F_BDAY = "bday"; private String url = "http://www.google.com"; public BDayWidgetModel(int instanceId){ super(instanceId); } public BDayWidgetModel(int instanceId, String inName, String inBady){ super(instanceId); this.name = inName; this.bday = inBady; } public void init(){} public String getName() { return name; } public void setName(String name) { this.name = name; } public String getBday() { return bday; } public void setBday(String bday) { this.bday = bday; } public long howManyDays(){ try{ return Utils.howfarInDays(Utils.getDate(this.bday)); }catch(ParseException x){ return 20000; } } public void setValueForPref(String key, String value){ if(key.equals(getStoredKeyForFieldName(BDayWidgetModel.F_NAME))){ this.name = value; } if(key.equals(getStoredKeyForFieldName(BDayWidgetModel.F_BDAY))){ this.bday = value; } } public String getPrefname(){ return BDayWidgetModel.BDAY_WIDGET_PROVIDER_NAME; } public Map getPrefsToSave(){ Map map = new HashMap(); map.put(BDayWidgetModel.F_NAME, this.name); map.put(BDayWidgetModel.F_BDAY, this.bday); return map; } public String toString(){ StringBuffer sbuf = new StringBuffer(); sbuf.append("iid:" + iid); sbuf.append("name:" + name); sbuf.append("bday:" + bday); return sbuf.toString(); } public static void clearAllPreferences(Context ctx){ APrefWidgetModel.clearAllPreferences(ctx, BDayWidgetModel.BDAY_WIDGET_PROVIDER_NAME); } public static BDayWidgetModel retrieveModel(Context ctx, int widgetId){ BDayWidgetModel m = new BDayWidgetModel(widgetId); boolean found = m.retrievePrefs(ctx); return found ? m : null; } public static void removePrefs(Context ctx, int widgetId){ BDayWidgetModel m = new BDayWidgetModel(widgetId); m.removePrefs(ctx); } }
(4)一些与日期相关的实用程序:Utils.java
package com.wangle.android.BDayWidget; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; public class Utils { private static String tag = "Utils"; public static Date getDate(String dataString) throws ParseException{ DateFormat a = getDateFormat(); Date date = a.parse(dataString); return date; } public static String test(String sdate){ try{ Date d = getDate(sdate); DateFormat a = getDateFormat(); String s = a.format(d); return s; }catch(Exception e){ return "problem with date:" + sdate; } } public static DateFormat getDateFormat(){ SimpleDateFormat df = new SimpleDateFormat("MM/dd/yyyy"); df.setLenient(false); return df; } public static boolean validateDate(String dateString){ try{ SimpleDateFormat df = new SimpleDateFormat("MM/dd/yyyy"); df.setLenient(false); Date date = df.parse(dateString); return true; }catch(ParseException x){ return false; } } public static long howfarInDays(Date date){ Calendar cal = Calendar.getInstance(); Date today = cal.getTime(); long today_ms = today.getTime(); long target_ms = date.getTime(); return (target_ms - today_ms)/(1000 * 60 * 60 * 24); } }
6、实现部件配置活动:
(1)ConfigureBDayWidgetActivity.java。参考上面说过的配置器活动做的8个工作内容。
package com.wangle.android.BDayWidget; import android.app.Activity; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.RemoteViews; public class ConfigureBDayWidgetActivity extends Activity{ private static String tag = "ConfigureBDayWidgetActivity"; private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; public void onCreate(Bundle saveInstanceStace){ super.onCreate(saveInstanceStace); setContentView(R.layout.edit_bday_widget); setupButton(); Intent intent = getIntent(); Bundle extras = intent.getExtras(); if(extras != null){ mAppWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); } } private void setupButton(){ Button b = (Button)this.findViewById(R.id.bdw_button_update_bday_widget); b.setOnClickListener(new Button.OnClickListener(){ public void onClick(View v){ parentButtonClicked(v); } }); } private void parentButtonClicked(View v){ String name = this.getName(); String date = this.getDate(); if(Utils.validateDate(date) == false){ this.setDate("wrong date:" + date); return; } if(this.mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID){ return; } updateAppWidgetLocal(name, date);
//我们将部件返回给调用方。AppWidgetManager就是通过这种方式知道配置器活动完成了对该部件实例的配置。 Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); setResult(RESULT_OK, resultValue); finish(); } private String getName(){ EditText namEdit = (EditText)this.findViewById(R.id.bdw_bday_name_id); String name = namEdit.getText().toString(); return name; } private String getDate(){ EditText dateEdit = (EditText)this.findViewById(R.id.bdw_bday_date_id); String dateString = dateEdit.getText().toString(); return dateString; } private void setDate(String errorDate){ EditText dateEdit = (EditText)this.findViewById(R.id.bdw_bday_date_id); dateEdit.setText("error"); dateEdit.requestFocus(); } private void updateAppWidgetLocal(String name, String dob){ BDayWidgetModel m = new BDayWidgetModel(mAppWidgetId, name, dob); updateAppWidget(this, AppWidgetManager.getInstance(this), m); m.savePreferences(this); } //RemoteViews类的使用 public static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, BDayWidgetModel widgetModel){ RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.bday_widget); views.setTextViewText(R.id.bdw_w_name, widgetModel.getName() + ":" + widgetModel.iid); views.setTextViewText(R.id.bdw_w_date, widgetModel.getBday()); views.setTextViewText(R.id.bdw_w_days, Long.toString(widgetModel.howManyDays())); Intent defineIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com")); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, defineIntent, 0); views.setOnClickPendingIntent(R.id.bdw_w_button_buy, pendingIntent); appWidgetManager.updateAppWidget(widgetModel.iid, views); } }
(2)配置器活动的布局文件:res/layout/edit_bday_widget.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/root_layout_id" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:id="@+id/bdw_text1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Name:"/> <EditText android:id="@+id/bdw_bday_name_id" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Anonymous"/> <TextView android:id="@+id/bdw_text2" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Birthday(9/1/2001):"/> <EditText android:id="@+id/bdw_bday_date_id" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="ex: 10/1/2009"/> <Button android:id="@+id/bdw_button_update_bday_widget" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="update"/> </LinearLayout>
7、main.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="fill_parent" android:orientation="vertical" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> </LinearLayout>