最近在项目里用到widget ,涉及到widget 的定时更新和事件监控与处理。具体效果如图:
功能:
实现定时更新
能展示同一组数据,update 时更新数据源
可以监听事件,并作出不同反应
实现分析:
SDK1.5 之后android:updatePeriodMillis 就失效,改为默认更新,为了实现自定义定时更新,用到了定时器主动更新;
widget 由其它应用程序托管,共用数据十分困难,我将数据写入sharedpreferences ,以记录数据源;
widget 各种状态都是由消息控制,为方便事件管理,向系统注册接受器。
首先在AndroidManifest.xml 文件中注册widget:
- <receiver
- android:name=".widget"
- android:label="@string/app_name"
- >
- <intent-filter>
- <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
- </intent-filter>
- <meta-data
- android:name="android.appwidget.provider"
- android:resource="@xml/widget"
- />
- </receiver>
添加res/xml/widget.xml设置widget属性:
- <appwidget-provider
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:minWidth="294dip"
- android:minHeight="72dip"
- android:updatePeriodMillis="5000"
- android:initialLayout="@layout/appwidget"
- >
- </appwidget-provider>
实现定时更新,只需在widget第一次创建或系统重启时开启计时器,于是给来自计时器的消息加上sheme以区分:
- public static final String URI_SCHEME = "widget";
- public void onReceive(Context context, Intent intent) {
- if(AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(intent.getAction()))
- final int[] appWidgetIds = intent.getExtras().getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
- for (int appWidgetId : appWidgetIds)
- {
- if(appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID)
- continue;
- //scheme不等于URI_SCHEME说明这个消息来自系统
- //定时更新或是widget第一次创建或是系统重启
- if (!URI_SCHEME.equals(intent.getScheme())) {
- setAlarm(context, appWidgetId, updateTime);
- }
- else {
- //可以在这里修改更新时间,如果有必要
- //例如:
- //setAlarm(context, appWidgetId, theValueComeFromPreference
- }
- }
- super.onReceive(context, intent);
- }
- }
-
- private void setAlarm(Context context, int appWidgetId, int updateRateSeconds) {
- Intent widgetUpdate = new Intent();
- widgetUpdate.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
- widgetUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { appWidgetId });
- //给来自计时器的消息加标识
- widgetUpdate.setData(Uri.withAppendedPath(Uri.parse(URI_SCHEME + "://widget/id/"), String.valueOf(appWidgetId)));
- PendingIntent newPending = PendingIntent.getBroadcast(context, 0, widgetUpdate, PendingIntent.FLAG_UPDATE_CURRENT);
- AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- if (updateRateSeconds >= 0) {
- alarms.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), updateRateSeconds, newPending);
- } else {
- alarms.cancel(newPending);
- }
- }
要接受来自计时器的消息,需要在AndroidManifest.xml另注册接受器:
- <receiver
- android:name=".widget"
- android:label="@string/app_name"
- >
- <intent-filter>
- <action android:name="com.example.widget.APPWIDGET_UPDATE" />
- <data android:scheme="widget" />
- </intent-filter>
- <meta-data android:name="android.appwidget.provider"
- android:resource="@xml/widget"
- />
- </receiver>
实现了定时更新。接下来展示界面:
- public static final String PREFS_NAME = "com.example.widget_preferences";
- public static final String PREFS_WIDGET_DATA = "WidgetData-%d";
- public static final String PREFS_CURRENT_INDEX = "CurrentIndex-%d";
- private static final String ACTION_WIDGET_CONTROL = "com.example.widget.WIDGET_CONTROL";
- public void onUpdate(Context context, AppWidgetManager appWidgetManager,
- int[] appWidgetIds) {
- // TODO Auto-generated method stub
- final int N = appWidgetIds.length;
- SharedPreferences shared = context.getSharedPreferences(PREFS_NAME, 0);
- SharedPreferences.Editor editor = shared.edit();
- for(int i = 0; i < N; i++)
- {
- if(appWidgetIds[i] == AppWidgetManager.INVALID_APPWIDGET_ID)
- continue;
- int appWidgetId = appWidgetIds[i];
- String widgetDataStr = “data1=value|||data2=value|||data3=value|||data4=value”;
- editor.putString(String.format(PREFS_WIDGET_DATA, appWidgetIds[i]), widgetDataStr);
- editor.putInt(String.format(PREFS_CURRENT_INDEX, appWidgetIds[i]), 0);
- updateDisplay(context, appWidgetId, getDataByKey(widgetDataStr, 0));
- }
- editor.commit();
- }
- private String[] getDataByKey(String dataStr, int index)
- {
- String[] strArr= dataStr.split("//|//|//|");
- if(index < 0)
- index = 0;
- if(index >= strArr.length)
- index = strArr.length - 1;
- strArr = strArr[index].split("=", 2);
- if(strArr.length == 2)
- return strArr;
- return new String[]{"",""};
- }
- public void updateDisplay(Context context, int appWidgetId, String[] playData)
- {
- RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.appwidget);
- remoteView.setTextViewText(R.id.widget_word, playData[0]);
- remoteView.setTextViewText(R.id.widget_meaning, playData[1]);
- remoteView.setOnClickPendingIntent(R.id.widget_back, makeControlPendingIntent(context, "back", appWidgetId));
- remoteView.setOnClickPendingIntent(R.id.widget_next, makeControlPendingIntent(context, "next", appWidgetId));
- AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, remoteView);
- }
- public PendingIntent makeControlPendingIntent(Context context, String command, int appWidgetId)
- {
- Intent action = new Intent();
- action.setAction(ACTION_WIDGET_CONTROL);
- action.putExtra(EXTRA_APPWIDGET_ID, appWidgetId);
- Uri data = Uri.withAppendedPath(Uri.parse(URI_SCHEME + "://widget/id/#" + command), String.valueOf(appWidgetId));
- action.setData(data);
- return (PendingIntent.getBroadcast(context, 0, action, PendingIntent.FLAG_ONE_SHOT));
- }
这样widget就可以正常显示了,当然,现在还无法接受消息去处理事件,接下来注册接受器:
- <receiver
- android:name=".widget"
- android:label="@string/app_name"
- >
- <intent-filter>
- <action android:name="com.example.widget.WIDGET_CONTROL" />
- <data android:scheme="widget" />
- </intent-filter>
- <meta-data android:name="android.appwidget.provider"
- android:resource="@xml/widget"
- />
- </receiver>
- public void onReceive(Context context, Intent intent)
- if(ACTION_WIDGET_CONTROL.equals(action))
- {
- final int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
- if(appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID)
- {
- String controlType = intent.getData().getFragment();
- SharedPreferences shared = context.getSharedPreferences(PREFS_NAME, 0);
- int index = shared.getInt(String.format(PREFS_CURRENT_INDEX, appWidgetId), 0);
- int size = Integer.parseInt(shared.getString(String.format(PREFS_DATA_SIZE, appWidgetId), "20"));
- if(controlType.equalsIgnoreCase("next"))
- {
- index++;
- if(index >= size)
- index = 0;
- }
- else if(controlType.equalsIgnoreCase("back"))
- {
- index--;
- if(index < 0)
- index = size - 1;
- }
- shared.edit().putInt(String.format(PREFS_CURRENT_INDEX, appWidgetId), index).commit();
- updateDisplay(
- context,
- appWidgetId,
- getDataByKey(shared.getString(String.format(PREFS_WIDGET_DATA, appWidgetId),""), index)
- );
- }
- }
- }
- 最后,不能忘了收尾工作:
- public void onDeleted(Context context, int[] appWidgetIds) {
- // TODO Auto-generated method stub
- SharedPreferences shared = context.getSharedPreferences(PREFS_NAME, 0);
- for(int appWidgetId : appWidgetIds)
- {
- setAlarm(context, appWidgetId, -1);
- shared.edit().remove(String.format(PREFS_WIDGET_DATA, appWidgetId))
- .remove(String.format(PREFS_CURRENT_INDEX, appWidgetId))
- .commit();
- }
- super.onDeleted(context, appWidgetIds);
- }
-