Android开发:如何刷新Widget中的ListView?
Android Widget ListView 刷新问题解决方案
问题描述
我用Eclipse开发一款Android应用,包含一个带ListView的Widget,ListView每行都有按钮,用于将行项保存到SQLite数据库。需要实现两种场景下更新这个ListView:
- 用户在主应用中更新列表时
- 用户点击Widget上的刷新ImageView时
尝试过多种方案,但都不适用于带Service的ListView Widget,求解决办法。
相关代码
WidgetProvider.class
public class WidgetProvider extends AppWidgetProvider { DBHelper pdh; public static final String ACTION_TOAST = "actionToast"; public static final String ACTION_BUTTON = "actionButton"; public static final String EXTRA_ITEM_POSITION = "extraItemPosition"; SimpleDateFormat sdfFinal = new SimpleDateFormat("yyyy-MM-dd"); SimpleDateFormat stf = new SimpleDateFormat("HH:mm"); @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { final int N = appWidgetIds.length; for (int i = 0; i < N; ++i) { RemoteViews remoteViews = updateWidgetListView(context, appWidgetIds[i]); Intent intent = new Intent(context, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); Intent clickIntentRefresh = new Intent(context, WidgetProvider.class); clickIntentRefresh.setAction(ACTION_BUTTON); PendingIntent clickPendingIntentRefresh = PendingIntent.getBroadcast(context, 0, clickIntentRefresh, 0); Intent clickIntent = new Intent(context, WidgetProvider.class); clickIntent.setAction(ACTION_TOAST); PendingIntent clickPendingIntent = PendingIntent.getBroadcast(context, 0, clickIntent, 0); remoteViews.setOnClickPendingIntent(R.id.ivRefresh,clickPendingIntentRefresh); remoteViews.setOnClickPendingIntent(R.id.button1, pendingIntent); remoteViews.setPendingIntentTemplate(R.id.listViewWidget, clickPendingIntent); appWidgetManager.updateAppWidget(appWidgetIds[i], remoteViews); } super.onUpdate(context, appWidgetManager, appWidgetIds); } @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); if (ACTION_TOAST.equals(intent.getAction())) { String s = intent.getStringExtra(EXTRA_ITEM_POSITION); pdh = new DBHelper(context); Calendar cal = Calendar.getInstance(); String strDateStamp = sdfFinal.format(cal.getTime()); String strTimeStamp = stf.format(cal.getTime()); pdh.insertRecord(s, null, strDateStamp, strTimeStamp); Toast.makeText(context, "Saved!", Toast.LENGTH_SHORT).show(); } else if (ACTION_BUTTON.equals(intent.getAction())) { Toast.makeText(context, "Refresh Clicked!", Toast.LENGTH_SHORT).show(); } } private RemoteViews updateWidgetListView(Context context, int appWidgetId) { RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout); Intent svcIntent = new Intent(context, WidgetService.class); svcIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); svcIntent.setData(Uri.parse(svcIntent.toUri(Intent.URI_INTENT_SCHEME))); remoteViews.setRemoteAdapter(R.id.listViewWidget, svcIntent); remoteViews.setEmptyView(R.id.listViewWidget, R.id.empty_view); return remoteViews; } }
WidgetService.class
public class WidgetService extends RemoteViewsService { /* * So pretty simple just defining the Adapter of the listview here Adapter is * ListProvider */ @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return (new ListProvider(this.getApplicationContext(), intent)); } }
ListProvider.class
public class ListProvider implements RemoteViewsFactory { public static final String EXTRA_ITEM_POSITION = "extraItemPosition"; private ArrayList<ListItem> listItemList = new ArrayList<ListItem>(); private Context context = null; private Intent intent; public ListProvider(Context context, Intent intent) { this.intent = intent; this.context = context; intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); populateListItem(); } private void populateListItem() { try { DBHelper pdh = new DBHelper(context); listItemList.clear(); SQLiteDatabase db = pdh.getReadableDatabase(); String[] columns = new String[] { "Categories" }; Cursor c = db.query("tblCategories", columns, null, null, null, null, "Categories asc"); int categoriesInt = c.getColumnIndex("Categories"); int totalCategories = c.getCount(); for (int i = 0; i < totalCategories; i++) { c.moveToPosition(i); ListItem listItem = new ListItem(); listItem.heading = c.getString(categoriesInt); listItemList.add(listItem); } c.close(); db.close(); pdh.close(); } catch (Exception ex) { } } @Override public int getCount() { return listItemList.size(); } @Override public long getItemId(int position) { return position; } /* * Similar to getView of Adapter where instead of View we return RemoteViews * */ @Override public RemoteViews getViewAt(int position) { try { final RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.list_row); ListItem listItem = (ListItem) listItemList.get(position); remoteView.setTextViewText(R.id.heading, listItem.heading); Intent fillIntent = new Intent(); fillIntent.putExtra(EXTRA_ITEM_POSITION, listItem.heading); remoteView.setOnClickFillInIntent(R.id.button1, fillIntent); return remoteView; } catch (Exception ex) { return null; } } @Override public RemoteViews getLoadingView() { // TODO Auto-generated method stub return null; } @Override public int getViewTypeCount() { // TODO Auto-generated method stub return 1; } @Override public boolean hasStableIds() { // TODO Auto-generated method stub return false; } @Override public void onCreate() { // TODO Auto-generated method stub } @Override public void onDataSetChanged() { // TODO Auto-generated method stub } @Override public void onDestroy() { // TODO Auto-generated method stub } }
ListItem.class
public class ListItem { public String heading; }
AndroidManifest.xml
<receiver android:name=".WidgetProvider" > <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/widgetinfo" /> </receiver> <service android:name=".WidgetService" android:permission="android.permission.BIND_REMOTEVIEWS" />
解决方案
带RemoteViewsService的Widget列表刷新,核心是触发RemoteViewsFactory重新加载数据,并通知AppWidgetManager更新,分以下几步修改:
1. 实现刷新按钮的更新逻辑
修改WidgetProvider的onReceive方法中ACTION_BUTTON分支,触发ListView数据刷新:
else if (ACTION_BUTTON.equals(intent.getAction())) { Toast.makeText(context, "刷新中...", Toast.LENGTH_SHORT).show(); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, WidgetProvider.class)); // 通知ListView数据已变更,触发onDataSetChanged appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.listViewWidget); // 重新执行onUpdate确保Widget配置正确 onUpdate(context, appWidgetManager, appWidgetIds); }
2. 让ListProvider支持数据刷新
在ListProvider中实现onDataSetChanged()方法,重新从数据库加载数据:
@Override public void onDataSetChanged() { // 重新获取最新数据 populateListItem(); }
3. 主应用更新数据后通知Widget
在主应用中,当完成SQLite数据更新后,发送广播触发Widget更新:
// 主应用更新数据后调用此代码 Intent updateIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); ComponentName widgetComponent = new ComponentName(context, WidgetProvider.class); updateIntent.setComponent(widgetComponent); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); int[] widgetIds = appWidgetManager.getAppWidgetIds(widgetComponent); updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds); context.sendBroadcast(updateIntent);
同时,在WidgetProvider的onUpdate方法中,循环里添加数据变更通知:
// 在appWidgetManager.updateAppWidget之前添加 appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds[i], R.id.listViewWidget);
4. 优化PendingIntent(避免缓存问题)
创建PendingIntent时添加FLAG_UPDATE_CURRENT(适配旧版本)或对应Android 12+的FLAG,防止Intent缓存:
比如刷新按钮的PendingIntent:
PendingIntent clickPendingIntentRefresh = PendingIntent.getBroadcast(context, 0, clickIntentRefresh, PendingIntent.FLAG_UPDATE_CURRENT);
5. 完善异常处理
在ListProvider的populateListItem方法中,不要空捕获异常,添加日志方便调试:
catch (Exception ex) { ex.printStackTrace(); }
内容的提问来源于stack exchange,提问作者George T.
相关产品推荐
相关产品推荐

