2012-12-04 49 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是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. 
    } 
} 

,最後,我的控件佈局:

<?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個自動添加的項目,如示例中所示。

配置活動完成後我無法更新該死的東西。從文檔中提取的這句話超出了我的意思:「當App Widget創建時不會調用onUpdate()方法[...] - 僅在第一次跳過時」。

我的問題是:

  • 爲什麼在世界上沒有Android的開發團隊選擇不調用update首次被創建的小部件?
  • 如何在配置活動完成之前更新我的應用程序小部件?

,我不明白另一件事是操作流程:

  1. 安裝編譯最後一個代碼的應用程序,在發射準備的空間,從發射
  2. 打開「小工具」菜單
  3. 選擇我的小部件,並將其放置在所需區域
  4. 在那一刻,我的應用程序控件提供者接收android.appwidget.action.APPWIDGET_ENABLED然後android.appwidget.action.APPWIDGET_UPDATE
  5. 然後我的應用程序控件提供者獲得其onUpdate方法調用。 我期望在配置活動結束後發生這種情況...
  6. 我的配置活動已開始。但應用程序小部件似乎已經創建和更新,我不明白。
  7. 我從我的配置活動中選擇項目:onListItemClick被稱爲
  8. 來自我的提供程序的靜態updateAppWidget被調用,拼命嘗試更新小部件。
  9. 配置活動設置其結果並結束。
  10. 供應商收到android.appwidget.action.APPWIDGET_UPDATE_OPTIONS:好吧,創建時收到尺寸更新確實有很大的意義。這就是我拼命撥打電話的地方updateAppWidget
  11. onUpdate我的提供者不會被調用。爲什麼??!!

最後:小部件是空的。不是listview-empty或@android:id/empty-empty,確實是10 EMPTY。沒有顯示視圖。沒有。
如果我再次安裝該應用程序,應用程序小部件將按照預期在列表視圖中填充視圖。
調整窗口小部件大小不起作用。它只是再次調用onAppWidgetOptionsChanged,這不起作用。

我的意思是空白:應用程序小部件佈局是誇大的,但是listview不是充氣的,空白的視圖不顯示。

+0

我沒有看到你的小部件配置xml和AndroidManifest.xml,你可以提供它們嗎? –

+0

我已經添加了「AndroidManifest.xml」相關部分,我將很快添加小部件配置xml(它尚未存放在源代碼庫中,因此直到幾個小時後才能訪問源代碼)。 –

回答

28

通過AppWidgetManager進行更新的缺點是您必須提供RemoteViews - 從設計的角度來看 - 沒有意義,因爲與RemoteView相關的邏輯應該封裝在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。

+1

這應該是正確的答案!它保存了我的一天;) – Mirko

+0

請在接受的答案之前嘗試這個答案,由於邏輯有些不同,它將我的應用程序引入了一些錯誤。這是我在配置活動之後使用的方法,並且效果很好。 –

+1

僅供參考@HenriqueSousa所說的'接受'答案是在他寫作喬納斯的回答時。我只是把伊曼紐爾的標記爲今天接受。 –

2

我沒有看到您的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()僅在完成後觸發。

+0

如果配置不正確,我的配置活動將不會顯示,對吧?我用我的清單條目更新了這個問題。 –

15

在配置活動完成後,沒有觸發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()at current返回一個空的RemoteViews對象。這就解釋了爲什麼你的widget首先完全是空的。你沒有填充任何視圖。我建議你從的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()之前的最後一步是更新集合視圖。正如評論所說,這不是第一次。但是,如果您打算允許在添加小部件後重新配置小部件,我會將其包含在內。在這種情況下,必須手動更新集合視圖以確保加載適當的視圖和數據。

+0

太棒了!輝煌!我想我在文檔上輸了一點。再次感謝。 –

相關問題