如何创建具有配置活动的应用程序窗口小部件,并首次进行更新? [英] How to create an app widget with a configuration activity, and update it for the first time?

查看:287
本文介绍了如何创建具有配置活动的应用程序窗口小部件,并首次进行更新?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这让我疯狂。我不知道如何从配置活动更新应用程序小部件,即使有推荐的做法。为什么没有在应用程序窗口小部件创建时调用更新方法超出了我的理解。



我想要的是:包含一个集合项目。但是用户需要选择一些东西,所以我需要一个配置活动。



配置活动是 ListActivity

  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class ChecksWidgetConfigureActivity extends SherlockListActivity {
private List<长> 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);
}

//如果他们给我们一个没有widget id的意图,只是保释。
if(mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID){
finish();
}

mRowIDs = new ArrayList< Long>(); //它实际上是从ASyncTask加载的,不用担心 - 它工作。
mAdapter = new MyListAdapter((LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE));
getListView()。setAdapter(mAdapter);
}

private class MyListAdapter extends BaseAdapter {
//不相关...
}

@Override
protected void onListItemClick(final ListView l,final View v,final int position,final long id){
if(position< mRowIDs.size()){
//设置窗口小部件结果
final Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,mAppWidgetId);
resultValue.putExtra(rowId,mRowIDs.get(position));
setResult(RESULT_OK,resultValue);

//请求窗口小部件更新
final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
ChecksWidgetProvider.updateAppWidget(this,appWidgetManager,mAppWidgetId,mRowIDs);
}

finish();
}
}

正如你所看到的,我调用静态方法从我的应用程序widget提供程序。我从官方文档



让我们来看看我的提供者:

  @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;

//为属于此提供者的每个App Widget执行此循环过程
for(int i = 0; i //这里我们设置指向StackViewService的意图,它将
//提供此集合的视图。
final Intent intent = new Intent(context,ChecksWidgetService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,appWidgetIds [i]);
//当比较intents时,extras被忽略,因此我们需要将extras
//嵌入到数据中,这样extras不会被忽略。
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);

//当集合没有项目时显示空视图。它应该是集合视图的兄弟
//。
rv.setEmptyView(android.R.id.list,android.R.id.empty);

//这里我们设置一个待处理的模板。集合
//的个人项目不能设置它们自己的待处理意向,而是集合作为一个整体可以
//设置一个待处理的意图模板,并且单个项目可以设置fillInIntent
//在项到项之间创建唯一的。
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 );
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,最终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 )时更新应用程序窗口小部件。



服务是基于集合的应用程序窗口小部件几乎是文档的完全复制/粘贴:

  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class ChecksWidgetService extends RemoteViewsService {
@Override
public RemoteViewsFactory onGetViewFactory(final Intent intent){
return new StackRemoteViewsFactory(this。 getApplicationContext(),intent);
}
}

类StackRemoteViewsFactory实现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(){
//在onCreate()中为数据源设置任何连接/光标。重载,
//例如下载或创建内容等,应推迟到onDataSetChanged()
//或getViewAt()。在此通话中占用超过20秒将导致ANR。
for(int i = 0; i mWidgetItems.add(new WidgetItem(i +(rowId:+ mRowId +)!
}

//这里我们睡3秒钟,以显示空视图在临时中出现的方式。
//空视图在StackWidgetProvider中设置,应该是
//集合视图的同级。
try {
Thread.sleep(3000);
} catch(final InterruptedException e){
e.printStackTrace();
}
}

@Override
public void onDestroy(){
//在onDestroy()中,你应该删除为你的数据源,
//例如。游标,连接等。
mWidgetItems.clear();
}

@Override
public int getCount(){
return mCount;
}

@Override
public RemoteViews getViewAt(final int position){
// position将始终从0到getCount() - 1.

//我们基于widget项目xml文件构造一个远程视图项目,并根据位置设置
//文本。
final RemoteViews rv = new RemoteViews(mContext.getPackageName(),R.layout.widget_item);
rv.setTextViewText(R.id.widget_item,mWidgetItems.get(position).text);

//接下来,我们设置一个fill-intent,它将用于填充在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);

//你可以在这里同步提升。例如,如果你需要
//处理一个图像,从网络中获取一些东西,等等,这是可以在这里做,
//同步。加载视图将显示为代替
//中的实际内容。
try {
L.d(载入视图+位置);
Thread.sleep(500);
} catch(final InterruptedException e){
e.printStackTrace();
}

//返回远程视图对象。
return rv;
}

@Override
public RemoteViews getLoadingView(){
//可以创建自定义加载视图(例如当getViewAt()很慢时)。你
//在这里返回null,你会得到默认的加载视图。
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(){
//这是在调用AppWidgetManager时触发的notifyAppWidgetViewDataChanged
//在集合视图对应到这个工厂。你可以在这里同步提升
//。例如,如果你需要处理一个图像,从网络等获取一些东西
//,这是可以在这里,同步。当在这里工作时,窗口小部件将保持
//的当前状态,所以你不需要担心
//锁定窗口部件。
}
}

最后,我的小部件布局:

 <?xml version =1.0encoding =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.0encoding =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 =horizo​​ntal | vertical
android:previewImage =@ drawable / resizeable_widget_preview/>






好吧,当我创建的小部件,它是空的。我的意思是void。空。没有。我没有在我的布局中定义的空视图!



如果我重新安装应用程序或重新启动设备(或杀死启动器应用程序),应用程序窗口小部件实际上已更新,并包含自动



在配置活动完成后,我无法获取更新的内容。这个句子,取自doc,是超越我的:当创建App Widget时,onUpdate()方法不会被调用[...]



我的问题是:





  • 如何在配置活动完成之前更新我的应用程序窗口小部件?



另一件我不明白的事情是行动流程:


  1. 安装最后编译好的应用程序,在启动器上准备空间,从启动器打开widgets菜单

  2. 选择我的小部件并将其放置到所需的区域

  3. 在那一刻,我的应用程序窗口小部件提供程序接收 android.appwidget.action.APPWIDGET_ENABLED ,然后 android.appwidget .action.APPWIDGET_UPDATE

  4. 然后,我的应用程序窗口小部件提供程序会调用其 onUpdate 方法。

  5. 我希望在配置活动完成后发生此情况。

  6. 我从配置活动中选择项目: onListItemClick 调用

  7. 调用我的提供程序中的静态 updateAppWidget ,试图更新窗口小部件。

  8. 配置活动设置其结果并完成。

  9. 提供程序接收 android.appwidget.action.APPWIDGET_UPDATE_OPTIONS 这确实使得在创建时接收大小更新很有意义。这是我绝望地呼叫 updateAppWidget

  10. onUpdate 从我的提供程序不被调用。为什么?

不是listview-empty或@android:id / empty-empty,真的 EMPTY 。不显示视图。没有任何内容。

如果我再次安装应用程序,应用程序窗口部件在列表视图内填充视图,如预期。

调整窗口小部件的大小没有效果。它再次调用 onAppWidgetOptionsChanged ,这没有任何效果。



我的意思是空的:

解决方案

通过AppWidgetManager执行更新的缺点是,你必须提供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 [] {m​​AppWidgetId});
sendBroadcast(intent);

因此,AppWidgetProvider的onUpdate()方法将被调用来为窗口小部件创建RemoteViews。 / p>

This is driving me crazy. I don't know how to update the app widget from the configuration activity, even with the recommended practises. Why the update method is not called on the app widget creation is beyond my understanding.

What I'd like: an app widget containing a collection (with a listview) of items. But the user needs to select something, so I need a configuration activity.

The configuration activity is a 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();
    }
}

As you can see I'm calling a static method from my app widget provider. I got that idea from the official doc.

Let's have a look at my provider:

@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);
    }
}

This is basically a copy/paste from the official doc. We can see my static method here. Let's pretend that it actually uses the rowId for now.

We can also see another failed (see below) attempt to update the app widget when I receive the options changed broadcast (onAppWidgetOptionsChanged).

The Service required for an app widget based on collections is almost an exact copy/paste of the doc:

@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.
    }
}

And at last, my widget layout:

<?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>

The relevant section of my android manifest XML file:

<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" />


So, what's wrong? Well, when I create the widget it's empty. I mean void. Empty. Nothing. I don't have the empty view defined in my layout! What the hell?

If I reinstall the app or reboot the device (or kill the launcher app), the app widget is actually updated and contains the 10 items that are automatically added as in the example.

I can't get the damn thing to update after the configuration activity finishes. This sentence, taken from the doc, is beyond me: "The onUpdate() method will not be called when the App Widget is created [...]—it is only skipped the first time.".

My question is:

  • Why in the world did the Android dev team chose not to call update for the first time the widget is created?
  • How can I update my app widget before the configuration activity finishes?

Another thing that I don't understand is the action flow:

  1. Install the app with the last code compiled, prepare space on the launcher, open the "widgets" menu from the launcher
  2. Choose my widget and place it to the desired area
  3. At that moment, my app widget provider receives android.appwidget.action.APPWIDGET_ENABLED and then android.appwidget.action.APPWIDGET_UPDATE
  4. Then my app widget provider gets its onUpdate method called. I expected this to happen AFTER the configuration activity finishes...
  5. My configuration activity gets started. But the app widget seems already created AND updated, which I don't understand.
  6. I choose the item from my configuration activity: onListItemClick gets called
  7. The static updateAppWidget from my provider is called, desperately trying to update the widget.
  8. The configuration activity sets its result and finishes.
  9. The provider receives android.appwidget.action.APPWIDGET_UPDATE_OPTIONS: well, that does make a lot of sense to receive a size update when created. That's where I call desperately updateAppWidget
  10. onUpdate from my provider is NOT called. Why??!!

In the end: the widget is empty. Not listview-empty or @android:id/empty-empty, really EMPTY. No view displayed. Nothing.
If I install the app again, the app widget is populated with views inside the listview, as expected.
Resizing the widget has no effect. It just calls onAppWidgetOptionsChanged again, which has no effect.

What I mean by empty: the app widget layout is inflated, but the listview is NOT inflated and the empty view is NOT displayed.

解决方案

The drawback of doing the update through the AppWidgetManager is that you have to provide the RemoteViews which - from a design point of view - doesn't make sense as the logic related to RemoteViews should be encapsulated within the AppWidgetProvider (or in your case in the RemoteViewsService.RemoteViewsFactory).

SciencyGuy's approach to expose the RemoteViews logic via a static method is one way to deal with that but there's a more elegant solution sending a broadcast directly to the widget:

Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, this, ChecksWidgetProvider.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {mAppWidgetId});
sendBroadcast(intent);

As a consequence the AppWidgetProvider's onUpdate() method will be called to create the RemoteViews for the widget.

这篇关于如何创建具有配置活动的应用程序窗口小部件,并首次进行更新?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆