我不知道如何从配置活动中更新应用程序小部件,即使使用推荐的实践。为什么在创建app小部件时不调用UPDATE方法,这是我所无法理解的。
我想要的是:一个应用程序小部件,包含一个项目集合(带有一个列表视图)。但是用户需要选择一些东西,所以我需要一个配置活动。
配置活动是ListActivity
:
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class ChecksWidgetConfigureActivity extends SherlockListActivity {
private List<Long> mRowIDs;
int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
private BaseAdapter mAdapter;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setResult(RESULT_CANCELED);
setContentView(R.layout.checks_widget_configure);
final Intent intent = getIntent();
final Bundle extras = intent.getExtras();
if (extras != null) {
mAppWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
}
// If they gave us an intent without the widget id, just bail.
if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish();
}
mRowIDs = new ArrayList<Long>(); // it's actually loaded from an ASyncTask, don't worry about that — it works.
mAdapter = new MyListAdapter((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE));
getListView().setAdapter(mAdapter);
}
private class MyListAdapter extends BaseAdapter {
// not relevant...
}
@Override
protected void onListItemClick(final ListView l, final View v, final int position, final long id) {
if (position < mRowIDs.size()) {
// Set widget result
final Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
resultValue.putExtra("rowId", mRowIDs.get(position));
setResult(RESULT_OK, resultValue);
// Request widget update
final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
ChecksWidgetProvider.updateAppWidget(this, appWidgetManager, mAppWidgetId, mRowIDs);
}
finish();
}
}
如您所见,我正在从我的app小部件提供程序调用一个静态方法。
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public class ChecksWidgetProvider extends AppWidgetProvider {
public static final String TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION";
public static final String EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM";
@Override
public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
final int N = appWidgetIds.length;
// Perform this loop procedure for each App Widget that belongs to this provider
for (int i = 0; i < N; i++) {
// Here we setup the intent which points to the StackViewService which will
// provide the views for this collection.
final Intent intent = new Intent(context, ChecksWidgetService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
// When intents are compared, the extras are ignored, so we need to embed the extras
// into the data so that the extras will not be ignored.
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.checks_widget);
rv.setRemoteAdapter(android.R.id.list, intent);
// The empty view is displayed when the collection has no items. It should be a sibling
// of the collection view.
rv.setEmptyView(android.R.id.list, android.R.id.empty);
// Here we setup the a pending intent template. Individuals items of a collection
// cannot setup their own pending intents, instead, the collection as a whole can
// setup a pending intent template, and the individual items can set a fillInIntent
// to create unique before on an item to item basis.
final Intent toastIntent = new Intent(context, ChecksWidgetProvider.class);
toastIntent.setAction(ChecksWidgetProvider.TOAST_ACTION);
toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
toastIntent.setData(Uri.parse(toastIntent.toUri(Intent.URI_INTENT_SCHEME)));
final PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
rv.setPendingIntentTemplate(android.R.id.list, toastPendingIntent);
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
}
}
@Override
public void onReceive(final Context context, final Intent intent) {
final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
if (intent.getAction().equals(TOAST_ACTION)) {
final int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
final long rowId = intent.getLongExtra("rowId", 0);
final int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
Toast.makeText(context, "Touched view " + viewIndex + " (rowId: " + rowId + ")", Toast.LENGTH_SHORT).show();
}
super.onReceive(context, intent);
}
@Override
public void onAppWidgetOptionsChanged(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId, final Bundle newOptions) {
updateAppWidget(context, appWidgetManager, appWidgetId, newOptions.getLong("rowId"));
}
public static void updateAppWidget(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId, final long rowId) {
final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.checks_widget);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
这基本上是来自官方文档的拷贝/粘贴。我们可以在这里看到我的静态方法。让我们假设它实际上使用了rowId
暂时的。
我们还可以看到另一次失败(见下文),试图在收到更改的广播选项时更新应用程序小部件(onAppWidgetOptionsChanged
)。Service
基于集合的应用程序小部件所需的几乎完全是文档的副本/粘贴:
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class ChecksWidgetService extends RemoteViewsService {
@Override
public RemoteViewsFactory onGetViewFactory(final Intent intent) {
return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
}
}
class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
private static final int mCount = 10;
private final List<WidgetItem> mWidgetItems = new ArrayList<WidgetItem>();
private final Context mContext;
private final int mAppWidgetId;
private final long mRowId;
public StackRemoteViewsFactory(final Context context, final Intent intent) {
mContext = context;
mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
mRowId = intent.getLongExtra("rowId", 0);
}
@Override
public void onCreate() {
// In onCreate() you setup any connections / cursors to your data source. Heavy lifting,
// for example downloading or creating content etc, should be deferred to onDataSetChanged()
// or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
for (int i = 0; i < mCount; i++) {
mWidgetItems.add(new WidgetItem(i + " (rowId: " + mRowId + ") !"));
}
// We sleep for 3 seconds here to show how the empty view appears in the interim.
// The empty view is set in the StackWidgetProvider and should be a sibling of the
// collection view.
try {
Thread.sleep(3000);
} catch (final InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onDestroy() {
// In onDestroy() you should tear down anything that was setup for your data source,
// eg. cursors, connections, etc.
mWidgetItems.clear();
}
@Override
public int getCount() {
return mCount;
}
@Override
public RemoteViews getViewAt(final int position) {
// position will always range from 0 to getCount() - 1.
// We construct a remote views item based on our widget item xml file, and set the
// text based on the position.
final RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);
rv.setTextViewText(R.id.widget_item, mWidgetItems.get(position).text);
// Next, we set a fill-intent which will be used to fill-in the pending intent template
// which is set on the collection view in StackWidgetProvider.
final Bundle extras = new Bundle();
extras.putInt(ChecksWidgetProvider.EXTRA_ITEM, position);
final Intent fillInIntent = new Intent();
fillInIntent.putExtras(extras);
rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);
// You can do heaving lifting in here, synchronously. For example, if you need to
// process an image, fetch something from the network, etc., it is ok to do it here,
// synchronously. A loading view will show up in lieu of the actual contents in the
// interim.
try {
L.d("Loading view " + position);
Thread.sleep(500);
} catch (final InterruptedException e) {
e.printStackTrace();
}
// Return the remote views object.
return rv;
}
@Override
public RemoteViews getLoadingView() {
// You can create a custom loading view (for instance when getViewAt() is slow.) If you
// return null here, you will get the default loading view.
return null;
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public long getItemId(final int position) {
return position;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public void onDataSetChanged() {
// This is triggered when you call AppWidgetManager notifyAppWidgetViewDataChanged
// on the collection view corresponding to this factory. You can do heaving lifting in
// here, synchronously. For example, if you need to process an image, fetch something
// from the network, etc., it is ok to do it here, synchronously. The widget will remain
// in its current state while work is being done here, so you don't need to worry about
// locking up the widget.
}
}
最后,我的小部件布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widgetLayout"
android:orientation="vertical"
android:padding="@dimen/widget_margin"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/resizeable_widget_title"
style="@style/show_subTitle"
android:padding="2dp"
android:paddingLeft="5dp"
android:textColor="#FFFFFFFF"
android:background="@drawable/background_pink_striked_transparent"
android:text="@string/show_title_key_dates" />
<ListView
android:id="@android:id/list"
android:layout_marginRight="5dp"
android:layout_marginLeft="5dp"
android:background="@color/timeline_month_dark"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textColor="#ffffff"
android:textStyle="bold"
android:text="@string/empty_view_text"
android:textSize="20sp" />
</LinearLayout>
我的Android清单XML文件的相关部分:
<receiver android:name="com.my.full.pkg.ChecksWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/checks_widget_info" />
</receiver>
<activity android:name="com.my.full.pkg.ChecksWidgetConfigureActivity">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<service
android:name="com.my.full.pkg.ChecksWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
xml/checks_widget_info.xml
:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="146dp"
android:minHeight="146dp"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/checks_widget"
android:configure="com.my.full.pkg.ChecksWidgetConfigureActivity"
android:resizeMode="horizontal|vertical"
android:previewImage="@drawable/resizeable_widget_preview" />
那么,怎么了?当我创建这个小部件时,它是空的。
如果我重新安装应用程序或重新启动设备(或关闭启动程序应用程序),应用程序小部件实际上是更新的,并包含了自动添加的10个条目,如本例所示。
在配置活动完成后,我无法得到更新的该死的东西。这句话是从医生那里拿来的,我无法理解:“创建AppWidget时不会调用onUpdate()方法...-它只是第一次跳过。“
我的问题是:
发布于 2018-01-29 14:53:57
通过AppWidgetManager进行更新的缺点是,您必须提供RemoteViews,从设计角度看,RemoteViews没有意义,因为与RemoteViews相关的逻辑应该封装在AppWidgetProvider中(或者在RemoteViewsService.RemoteViewsFactory中)。
SciencyGuy通过静态方法公开RemoteViews逻辑的方法是解决这一问题的一种方法,但是有一种更优雅的解决方案,将广播直接发送到小部件:
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, this, ChecksWidgetProvider.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {mAppWidgetId});
sendBroadcast(intent);
因此,将调用AppWidgetProvider的onUpdate()方法来为小部件创建RemoteViews。
发布于 2018-01-29 16:16:23
在配置活动完成后,没有触发onUpdate-方法,这是正确的。这取决于您的配置活动来执行初始更新。因此,您需要构建初始视图。
这是在配置结束时应该做的事情的要点:
// First set result OK with appropriate widgetId
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
setResult(RESULT_OK, resultValue);
// Build/Update widget
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getApplicationContext());
// This is equivalent to your ChecksWidgetProvider.updateAppWidget()
appWidgetManager.updateAppWidget(appWidgetId,
ChecksWidgetProvider.buildRemoteViews(getApplicationContext(),
appWidgetId));
// Updates the collection view, not necessary the first time
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.notes_list);
// Destroy activity
finish();
您已经正确地设置了结果。你打电话给ChecksWidgetProvider。但是updateAppWidget()没有返回正确的结果。
updateAppWidget()在当前返回一个空的RemoteViews-对象。这解释了为什么小部件一开始完全是空的。你什么都没给这景色填满。我建议您将代码从onUpdate移动到静态buildRemoteViews()方法,可以从onUpdate和updateAppWidget()调用该方法:
public static RemoteViews buildRemoteViews(final Context context, final int appWidgetId) {
final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.checks_widget);
rv.setRemoteAdapter(android.R.id.list, intent);
// The empty view is displayed when the collection has no items. It should be a sibling
// of the collection view.
rv.setEmptyView(android.R.id.list, android.R.id.empty);
// Here we setup the a pending intent template. Individuals items of a collection
// cannot setup their own pending intents, instead, the collection as a whole can
// setup a pending intent template, and the individual items can set a fillInIntent
// to create unique before on an item to item basis.
final Intent toastIntent = new Intent(context, ChecksWidgetProvider.class);
toastIntent.setAction(ChecksWidgetProvider.TOAST_ACTION);
toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
toastIntent.setData(Uri.parse(toastIntent.toUri(Intent.URI_INTENT_SCHEME)));
final PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
rv.setPendingIntentTemplate(android.R.id.list, toastPendingIntent);
return rv;
}
public static void updateAppWidget(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId) {
final RemoteViews views = buildRemoteViews(context, appWidgetId);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@Override
public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
// Perform this loop procedure for each App Widget that belongs to this provider
for (int appWidgetId: appWidgetIds) {
RemoteViews rv = buildRemoteViews(context, appWidgetId);
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
}
}
这应该负责小部件的初始化。
在我的示例代码中调用Finish()之前的最后一步是更新集合视图。正如评论所言,这并不是第一次。但是,我包括它,以防您打算在添加小部件之后重新配置它。在这种情况下,必须手动更新集合视图,以确保加载了适当的视图和数据。
https://stackoverflow.com/questions/-100007257
复制相似问题