2012-12-04 21 views
18

これは私を夢中にしています。推奨されている方法であっても、設定アクティビティからアプリウィジェットを更新する方法はわかりません。アプリウィジェットの作成時に更新メソッドが呼び出されないのはなぜか分かりません。設定アクティビティでアプリウィジェットを作成して初めて更新する方法は?

私が望むもの:アイテムのコレクション(リストビュー付き)を含むアプリウィジェットです。しかし、ユーザーは何かを選択する必要があるので、私は設定作業が必要です。

構成アクティビティーは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(); 
    } 
} 

あなたは、私は私のアプリウィジェットプロバイダから静的メソッドを呼んでいる見ることができるように。私はその考えをthe official docから得ました。

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

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

関連セクション私のアンドロイドマニフェスト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個の項目が含まれます。

設定作業が完了したら、私は気になることはありません。ドキュメントから取ったこの文は、私以外のものです: "Appウィジェットが作成されたときにonUpdate()メソッドは呼び出されません[...] - 最初にスキップされるのは"です。

私の質問は次のとおりです。

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

もう一つは、アクションの流れている:

  1. は、コンパイルされた最後のコードでアプリをインストールランチャーのスペースを用意し、ランチャーから「ウィジェット」メニューを開き
  2. 私のウィジェットを選択して、その瞬間、所望の領域
  3. にそれを置き、私のアプリウィジェットプロバイダがandroid.appwidget.action.APPWIDGET_ENABLEDを受信しandroid.appwidget.action.APPWIDGET_UPDATE
  4. そして、私のアプリウィジェットプロバイダがを取得しますメソッドが呼び出されました。 設定アクティビティが完了した後にこれが起こることが期待されました...
  5. 設定作業が開始されます。しかし、アプリウィジェットは既に作成されて更新されているようですが、私は理解しません。
  6. 私は私の構成アクティビティーから項目を選択します。onListItemClickは必死にウィジェットを更新しようと、
  7. 私のプロバイダから静的updateAppWidget
  8. が呼び出される呼び出されます。
  9. 構成アクティビティは結果を設定して終了します。
  10. プロバイダはandroid.appwidget.action.APPWIDGET_UPDATE_OPTIONSを受け取っています。それは、作成時にサイズの更新を受け取ることに多くの意味があります。それは私が必死に呼ぶところです。updateAppWidget
  11. onUpdate私の提供元からは電話がかかっていません。なぜ??!!

最後に:ウィジェットは空です。 listview-emptyまたは@android:id/empty-empty、実際にはEMPTYです。ビューが表示されません。何もない。
アプリをもう一度インストールすると、アプリウィジェットにはリストビュー内のビューが予想通りに取り込まれます。
ウィジェットのサイズを変更しても効果はありません。もう一度onAppWidgetOptionsChangedを呼び出しても効果はありません。

空を意味する:アプリウィジェットのレイアウトは膨らんでいますが、リストビューは膨らんでいないし、空のビューは表示されません。

+0

私はあなたにもそれらを提供することができ、あなたのウィジェットの設定XMLとのAndroidManifest.xmlが表示されませんか? –

+0

私は(私は今から数時間までソースにアクセスすることはできませんので、それはソースリポジトリにはまだありません)私はすぐにウィジェット構成のXMLを追加します、 'AndroidManifest.xml'関連セクションを追加しました。 –

答えて

28

AppWidgetManagerを介して更新を行うことの欠点は、RemoteViewsに関連するロジックがAppWidgetProvider内にカプセル化される必要があるため、(設計上の観点から)RemoteViewsを提供する必要があることですRemoteViewsService.RemoteViewsFactoryのケース)。静的メソッドを介してRemoteViewsロジックを公開する

SciencyGuyのアプローチは、それに対処するための一つの方法ですが、ウィジェットに直接ブロードキャストを送信するよりエレガントな解決策があります。その結果AppWidgetProviderのにonUpdate(として

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

)メソッドが呼び出され、ウィジェットのRemoteViewが作成されます。

+1

これは正解です!それは私の日を救った;) – Mirko

+0

私のアプリを多少異なるロジックのためにいくつかのバグに導入した、受け入れる前にこの答えを試してください。これは、設定アクティビティの後に使用するメソッドであり、うまく動作します。 –

+1

FYI「受け入れられた」答え@HenriqueSousaは、彼のジョナスの答えを書いていた時点を指しています。私は今、受け入れられたようにEmanuel'sをマークしました。 –

2

あなたのappwidgetprovider.xmlとAndroidManifest.xmlが表示されませんでしたが、あなたの設定作業を適切に設定していないと思います。ここで

はそれを行う方法は次のとおりです。あなたの設定の活動が適切なintent-filterを持っている必要があり

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" 
    ... 
    android:configure="com.full.package.name.ChecksWidgetConfigureActivity" 
    ... /> 
  • <activity android:name=".ChecksWidgetConfigureActivity"> 
        <intent-filter> 
         <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/> 
        </intent-filter> 
    </activity> 
    
    1. はあなたのappwidgetprovider.xmlに次の属性を追加します。

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

    +0

    これが正しく設定されていないと、設定作業が表示されませんでした。私はマニフェスト項目で質問を更新しました。 –

    15

    設定アクティビティが完了した後で、onUpdateメソッドがトリガされないことは間違いありません。最初の更新を行うのは、構成活動までです。初期ビューを作成する必要があります。

    これは、1つのコンフィギュレーションの最後に何をすべきかの要旨です:

    // 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とupdateAppWidget(両方から呼び出すことができます()メソッド)静的buildRemoteViewsににonUpdateから、あなたのコードを移動することを示唆している:

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

    +0

    これは素晴らしいことでした!ブリリアント!私はドキュメンテーションで少し失われたと思う。再度、感謝します。 –

    関連する問題