web-dev-qa-db-ja.com

構成アクティビティを使用してアプリウィジェットを作成し、それを初めて更新するにはどうすればよいですか?

これは私を夢中にさせています。推奨される方法を使用しても、構成アクティビティからアプリウィジェットを更新する方法がわかりません。アプリウィジェットの作成時に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();
    }
}

ご覧のとおり、アプリウィジェットプロバイダーから静的メソッドを呼び出しています。私はそのアイデアを 公式ドキュメント から得ました。

私のプロバイダーを見てみましょう:

@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個のアイテムが含まれます。

構成アクティビティが終了した後、いまいましいものを更新することができません。ドキュメントから抜粋したこの文は、私を超えています。 "アプリウィジェットの作成時にonUpdate()メソッドは呼び出されません[...]-最初にスキップされるだけです。」。

私の質問は:

  • なぜ世界でAndroid開発チームはウィジェットが初めて作成されたときにupdateを呼び出さないことを選択したのですか?
  • 構成アクティビティが完了する前にアプリウィジェットを更新するにはどうすればよいですか?

私が理解していないもう1つのことは、アクションフローです。

  1. 最後にコンパイルされたコードでアプリをインストールし、ランチャーにスペースを準備し、ランチャーから「ウィジェット」メニューを開きます
  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を呼び出すだけで、効果はありません。

空とはどういう意味ですか:アプリウィジェットのレイアウトは膨らんでいますが、リストビューは膨らんでおらず、空のビューは表示されていません。

20
Benoit Duffez

AppWidgetManagerを介して更新を行うことの欠点は、RemoteViewを提供する必要があることです。これは、設計の観点からは、RemoteViewに関連するロジックをAppWidgetProvider内にカプセル化する必要があるため(または、あなたの場合はRemoteViewsService.RemoteViewsFactory)。

静的メソッドを介してRemoteViewsロジックを公開するSciencyGuyのアプローチは、これに対処する1つの方法ですが、ウィジェットに直接ブロードキャストを送信する、より洗練されたソリューションがあります。

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()メソッドが呼び出され、ウィジェットのRemoteViewが作成されます。

30

構成アクティビティの終了後に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()は正しい結果を返しません。

updateAppWidget()は現在、空のRemoteViewsオブジェクトを返します。これが、ウィジェットが最初は完全に空である理由を説明しています。ビューを何も埋めていません。コードをonUpdateから、onUpdateとupdateAppWidget()の両方から呼び出すことができる静的なbuildRemoteViews()メソッドに移動することをお勧めします。

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()を呼び出す前の最後のステップは、コレクションビューを更新することです。コメントが言うように、これは最初は必要ありません。ただし、ウィジェットが追加された後にウィジェットを再構成できるようにする場合に備えて、これを含めます。その場合、コレクションビューを手動で更新して、適切なビューとデータが読み込まれるようにする必要があります。

15

Appwidgetprovider.xmlとAndroidManifest.xmlが表示されませんでしたが、構成アクティビティが正しく設定されていなかったと思います。

方法は次のとおりです。

  1. appwidgetprovider.xmlに次の属性を追加します。

    _<appwidget-provider xmlns:Android="http://schemas.Android.com/apk/res/Android"
        ...
        Android:configure="com.full.package.name.ChecksWidgetConfigureActivity" 
        ... />
    _
  2. 構成アクティビティには適切な_intent-filter_が必要です。

    _<activity Android:name=".ChecksWidgetConfigureActivity">
        <intent-filter>
            <action Android:name="Android.appwidget.action.APPWIDGET_CONFIGURE"/>
        </intent-filter>
    </activity>
    _

構成アクティビティが正しく構成されている場合、onUpdate()は終了後にのみトリガーされます。

2

構成、オプション、または設定機能を使用してウィジェットを作成する方法を説明する最新の例を探している開発者については、 http://www.zoftino.com/Android-widget-example を参照してください。

構成機能を開発するには、ユーザーがウィジェットを構成できるようにする構成アクティビティとUIをアプリで作成する必要があります。ウィジェット構成オプションは、ウィジェットのインスタンスが作成されたとき、またはウィジェットがクリックされるたびに表示できます。ウィジェットの設定を変更するたびに、変更をウィジェットインスタンスに適用する必要があります。

0
Arnav Rao