ListView未在Android Widget中更新

问题描述:

我有一个具有项目ListView的小部件。当小部件是新的且空的时候,用户点击它以加载具有对象列表的活动(每个对象包含项目列表)以选择对象,然后小部件应该接收它并更新其内容以显示它。我设法达到AppWidgetProvider收到对象(包含列表)并调用更新的阶段。我没有做的是让提供者调用RemoteViewService和进一步的步骤。我将包含类和XML以供审阅。ListView未在Android Widget中更新

的AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
      package="io.litebit.ilfornodellacasa"> 

    <uses-permission android:name="android.permission.INTERNET"/> 

    <application 
     android:allowBackup="true" 
     android:icon="@mipmap/ic_launcher" 
     android:label="@string/app_name" 
     android:roundIcon="@mipmap/ic_launcher_round" 
     android:supportsRtl="true" 
     android:theme="@style/AppTheme"> 
     <activity 
      android:name=".ui.activities.MainActivity" 
      android:launchMode="singleTop"> 
      <intent-filter> 
       <action android:name="android.intent.action.MAIN"/> 

       <category android:name="android.intent.category.LAUNCHER"/> 
      </intent-filter> 
     </activity> 
     <activity 
      android:name=".ui.activities.RecipeActivity" 
      android:launchMode="singleTop"> 
      <meta-data 
       android:name="android.support.PARENT_ACTIVITY" 
       android:value=".ui.activities.MainActivity"/> 
     </activity> 

     <receiver android:name=".ui.widgets.IngredientsWidgetProvider"> 
      <intent-filter> 
       <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> 
      </intent-filter> 

      <meta-data 
       android:name="android.appwidget.provider" 
       android:resource="@xml/ingredients_app_widget_info"/> 
     </receiver> 

     <service 
      android:name=".ui.widgets.WidgetService" 
      android:exported="false" 
      android:permission="android.permission.BIND_REMOTEVIEWS"/> 
    </application> 

</manifest> 

IngredientsWidgetProvider.java

package io.litebit.ilfornodellacasa.ui.widgets; 

import android.app.PendingIntent; 
import android.appwidget.AppWidgetManager; 
import android.appwidget.AppWidgetProvider; 
import android.content.Context; 
import android.content.Intent; 
import android.net.Uri; 
import android.util.Log; 
import android.view.View; 
import android.widget.RemoteViews; 

import io.litebit.ilfornodellacasa.R; 
import io.litebit.ilfornodellacasa.model.Recipe; 
import io.litebit.ilfornodellacasa.ui.activities.MainActivity; 
import io.litebit.ilfornodellacasa.ui.activities.RecipeActivity; 

import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID; 

/** 
* Implementation of App Widget functionality. 
*/ 
public class IngredientsWidgetProvider extends AppWidgetProvider { 

    private static final String TAG = IngredientsWidgetProvider.class.getSimpleName(); 
    private static Recipe recipe; 

    static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, 
           int appWidgetId) { 

     // Construct the RemoteViews object 
     RemoteViews widget = new RemoteViews(context.getPackageName(), 
       R.layout.ingredients_app_widget); 

     // Create pending intent to open the MainActivity 
     Intent mainActivityIntent = new Intent(context, MainActivity.class); 
     mainActivityIntent.putExtra(EXTRA_APPWIDGET_ID, appWidgetId); 
     mainActivityIntent.setAction(MainActivity.ACTION_UPDATE_WIDGET); 

     PendingIntent pendingIntent = PendingIntent.getActivity(context, 
       0, mainActivityIntent, 0); 

     // Launch pending intent on click 
     widget.setOnClickPendingIntent(R.id.widget_layout, pendingIntent); 


     if (recipe != null) { 
      Log.i(TAG, "Recipe: " + recipe.getName() + " to be visualized"); 
      widget.setViewVisibility(R.id.tv_widget_empty, View.GONE); 
      widget.setViewVisibility(R.id.lv_widget, View.VISIBLE); 

      Intent listIntent = new Intent(context, WidgetService.class); 
      listIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 
      listIntent.putExtra(RecipeActivity.KEY_RECIPE, recipe); 
      Uri uri = Uri.parse(listIntent.toUri(Intent.URI_INTENT_SCHEME)); 
      listIntent.setData(uri); 

      widget.setRemoteAdapter(R.id.lv_widget, listIntent); 
      widget.setEmptyView(R.id.lv_widget, R.id.tv_widget_empty); 
     } else { 
      widget.setViewVisibility(R.id.tv_widget_empty, View.VISIBLE); 
      widget.setViewVisibility(R.id.lv_widget, View.GONE); 
     } 

     // Instruct the widget manager to update the widget 
     appWidgetManager.updateAppWidget(appWidgetId, widget); 
    } 

    @Override 
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { 
     // There may be multiple widgets active, so update all of them 
     for (int appWidgetId : appWidgetIds) { 
      updateAppWidget(context, appWidgetManager, appWidgetId); 
     } 
    } 

    @Override 
    public void onEnabled(Context context) { 
     // Enter relevant functionality for when the first widget is created 
    } 

    @Override 
    public void onDisabled(Context context) { 
     // Enter relevant functionality for when the last widget is disabled 
    } 

    @Override 
    public void onReceive(Context context, Intent intent) { 
     super.onReceive(context, intent); 
     recipe = intent.getParcelableExtra(RecipeActivity.KEY_RECIPE); 
     if (recipe != null) { 
      Log.i(TAG, "Recipe: " + recipe.getName() + " selected"); 
      updateAppWidget(context, AppWidgetManager.getInstance(context), 
        intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 
          AppWidgetManager.INVALID_APPWIDGET_ID)); 
     } 
    } 
} 

IngredientViewHolderFactory.java

package io.litebit.ilfornodellacasa.ui.widgets; 

import android.content.Context; 
import android.util.Log; 
import android.widget.RemoteViews; 
import android.widget.RemoteViewsService; 

import java.util.ArrayList; 
import java.util.List; 

import io.litebit.ilfornodellacasa.R; 
import io.litebit.ilfornodellacasa.model.Ingredient; 
import io.litebit.ilfornodellacasa.ui.utils.Utils; 

/** 
* Copyright 2017 Ramy Bittar 
* 
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0 
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License. 
*/ 

public class IngredientViewHolderFactory implements RemoteViewsService.RemoteViewsFactory { 

    private static final String TAG = IngredientViewHolderFactory.class.getSimpleName(); 
    private List<Ingredient> ingredients = new ArrayList<>(); 
    private Context context; 
    private int appWidgetId; 
    private Utils utils; 

    IngredientViewHolderFactory(Context context, List<Ingredient> ingredients, int appWidgetId) { 

     this.context = context; 
     this.appWidgetId = appWidgetId; 
     this.ingredients = ingredients; 
     utils = new Utils(context); 
     Log.i(TAG, "Public constructor"); 
    } 

    @Override 
    public void onCreate() { 
     Log.i(TAG, "appWidgetId = " + this.appWidgetId); 
    } 

    @Override 
    public void onDataSetChanged() { 

    } 

    @Override 
    public void onDestroy() { 

    } 

    @Override 
    public int getCount() { 
     if (ingredients != null) { 
      return ingredients.size(); 
     } 
     return 0; 
    } 

    @Override 
    public RemoteViews getViewAt(int i) { 
     final RemoteViews viewHolder = new RemoteViews(context.getPackageName(), 
       R.layout.viewholder_ingredient); 
     Ingredient ingredient = ingredients.get(i); 
     viewHolder.setTextViewText(R.id.tv_ingredient, ingredient.getIngredient()); 
     String quantity = utils.getQuantity(
       ingredient.getQuantity(), 
       ingredient.getMeasure(), 
       Utils.UNIT_SYS_IMPERIAL); 
     viewHolder.setTextViewText(R.id.tv_quantity, quantity); 

     return viewHolder; 
    } 

    @Override 
    public RemoteViews getLoadingView() { 
     return null; 
    } 

    @Override 
    public int getViewTypeCount() { 
     return 1; 
    } 

    @Override 
    public long getItemId(int position) { 
     return position; 
    } 

    @Override 
    public boolean hasStableIds() { 
     return false; 
    } 
} 

WidgetService.java

package io.litebit.ilfornodellacasa.ui.widgets; 

import android.appwidget.AppWidgetManager; 
import android.content.Intent; 
import android.util.Log; 
import android.widget.RemoteViewsService; 

import io.litebit.ilfornodellacasa.model.Recipe; 
import io.litebit.ilfornodellacasa.ui.activities.RecipeActivity; 

/** 
* Copyright 2017 Ramy Bittar 
* 
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0 
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License. 
*/ 

public class WidgetService extends RemoteViewsService { 
    private static final String TAG = WidgetService.class.getSimpleName(); 

    /** 
    * Invokes the remote view factory 
    * @param intent passed from the calling widget provider to the remote view factory 
    * @return RemoteViewsFactory object 
    */ 
    @Override 
    public RemoteViewsFactory onGetViewFactory(Intent intent) { 
     Log.i(TAG, "onGetViewFactory called"); 
     Recipe recipe = intent.getParcelableExtra(RecipeActivity.KEY_RECIPE); 
     int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 
       AppWidgetManager.INVALID_APPWIDGET_ID); 
     return (new IngredientViewHolderFactory(this.getApplicationContext(), 
       recipe.getIngredients(), 
       appWidgetId)); 
    } 
} 

MainActivity.java

package io.litebit.ilfornodellacasa.ui.activities; 

import android.app.PendingIntent; 
import android.appwidget.AppWidgetManager; 
import android.content.Intent; 
import android.content.res.Configuration; 
import android.os.Bundle; 
import android.support.v7.app.AppCompatActivity; 
import android.support.v7.widget.GridLayoutManager; 
import android.support.v7.widget.LinearLayoutManager; 
import android.support.v7.widget.RecyclerView; 
import android.util.Log; 
import android.view.Menu; 
import android.view.MenuItem; 
import android.widget.Toast; 

import java.util.List; 

import io.litebit.ilfornodellacasa.R; 
import io.litebit.ilfornodellacasa.model.Recipe; 
import io.litebit.ilfornodellacasa.model.RecipeListSerializer; 
import io.litebit.ilfornodellacasa.sync.RecipeSyncTask; 
import io.litebit.ilfornodellacasa.ui.adapters.RecipeAdapter; 
import io.litebit.ilfornodellacasa.ui.widgets.IngredientsWidgetProvider; 
import pocketknife.BundleSerializer; 
import pocketknife.PocketKnife; 
import pocketknife.SaveState; 

/** 
* Copyright 2017 Ramy Bittar 
* 
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0 
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License. 
*/ 

public class MainActivity extends AppCompatActivity implements RecipeSyncTask.SyncRecipesCallback, 
     RecipeAdapter.OnRecipeClicked { 

// private static final String TAG = MainActivity.class.getSimpleName(); 

    private static final String TAG = MainActivity.class.getSimpleName(); 
    public static final String ACTION_UPDATE_WIDGET = TAG + ".action.update_widget"; 

    private boolean updateWidget = false; 
    private int appWidgetId; 
    private RecipeAdapter adapter; 

    @SaveState 
    @BundleSerializer(RecipeListSerializer.class) 
    List<Recipe> recipes; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 
     PocketKnife.bindExtras(this); 
     PocketKnife.restoreInstanceState(this, savedInstanceState); 

     RecyclerView recyclerView = findViewById(R.id.recycler_view); 
     RecyclerView.LayoutManager layoutManager; 
     if (isTabletAndLandscape()) { 
      layoutManager = new GridLayoutManager(this, 3); 
     } else { 
      layoutManager = new LinearLayoutManager(this); 
     } 
     recyclerView.setLayoutManager(layoutManager); 

     adapter = new RecipeAdapter(null, this); 
     recyclerView.setAdapter(adapter); 

     if (recipes == null || recipes.size() == 0) { 
      RecipeSyncTask syncTask = new RecipeSyncTask(this); 
      syncTask.syncRecipes(); 
     } else { 
      refreshRecyclerView(recipes); 
     } 

     if (!getIntent().getAction().equals("")) { 
      updateWidget = getIntent().getAction().equals(ACTION_UPDATE_WIDGET); 
      appWidgetId = getIntent().getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0); 
     } 

     Log.i(TAG, "updateWidget = " + updateWidget); 
    } 

    private boolean isTabletAndLandscape() { 
     return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE 
       && getResources().getConfiguration().screenWidthDp >= 900; 
    } 

    private void refreshRecyclerView(List<Recipe> recipes) { 
     this.adapter.switchData(recipes); 
    } 

    @Override 
    public boolean onCreateOptionsMenu(Menu menu) { 
     getMenuInflater().inflate(R.menu.menu, menu); 
     return true; 
    } 

    @Override 
    public boolean onOptionsItemSelected(MenuItem item) { 
     switch (item.getItemId()) { 
      case R.id.action_settings: 
       return true; 
      default: 
       return super.onOptionsItemSelected(item); 
     } 
    } 

    @Override 
    protected void onSaveInstanceState(Bundle outState) { 
     PocketKnife.saveInstanceState(this, outState); 
     super.onSaveInstanceState(outState); 
    } 

    @Override 
    public void onSyncResponse(List<Recipe> newRecipes) { 
     recipes = newRecipes; 
     refreshRecyclerView(this.recipes); 
     Toast.makeText(this, recipes.size() + " recipe(s) found.", Toast.LENGTH_SHORT).show(); 
    } 

    @Override 
    public void onSyncFailure(Throwable throwable) { 
     Toast.makeText(this, "Something wrong happened. Check system log for details.", 
       Toast.LENGTH_SHORT).show(); 
    } 

    @Override 
    public void onClick(int recipeId) { 
     Recipe currentRecipe = null; 
     for (Recipe recipe : recipes) { 
      if (recipe.getId() == recipeId) { 
       currentRecipe = recipe; 
      } 
     } 
     if (updateWidget) { 
      sendIntentToWidget(currentRecipe); 
     } else { 
      sendIntentToRecipeActivity(currentRecipe); 
     } 
    } 

    private void sendIntentToWidget(Recipe currentRecipe) { 
     Intent recipeIntent = new Intent(this, IngredientsWidgetProvider.class); 
     recipeIntent.putExtra(RecipeActivity.KEY_RECIPE, currentRecipe); 
     recipeIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 
     recipeIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 
     sendBroadcast(recipeIntent); 
     finish(); 
    } 

    private void sendIntentToRecipeActivity(Recipe currentRecipe) { 
     Intent recipeIntent = new Intent(this, RecipeActivity.class); 
     recipeIntent.putExtra(RecipeActivity.KEY_RECIPE, currentRecipe); 
     recipeIntent.putExtra(RecipeActivity.KEY_STEP_ID, RecipeActivity.NO_STEP_SELECTED); 
     startActivity(recipeIntent); 
    } 
} 

viewholder_ingredient.xml

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
       android:layout_width="match_parent" 
       android:layout_height="wrap_content" 
       android:orientation="vertical" 
       android:paddingTop="10dp" 
       android:paddingBottom="0dp" 
       android:paddingLeft="14dp" 
       android:paddingRight="14dp"> 

    <TextView 
     android:id="@+id/tv_ingredient" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:textStyle="bold"/> 

    <TextView 
     android:id="@+id/tv_quantity" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content"/> 
</LinearLayout> 

ingredients_app_widget.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
       xmlns:tools="http://schemas.android.com/tools" 
       android:id="@+id/widget_layout" 
       android:layout_width="match_parent" 
       android:layout_height="match_parent" 
       android:background="#66ffffff" 
       android:orientation="vertical" 
       android:padding="@dimen/widget_margin"> 

    <ListView 
     android:id="@+id/lv_widget" 
     android:layout_width="match_parent" 
     android:layout_height="match_parent" 
     android:visibility="visible"/> 

    <TextView 
     android:id="@+id/tv_widget_empty" 
     android:layout_width="match_parent" 
     android:layout_height="match_parent" 
     android:text="@string/click_to_select_a_recipe" 
     android:visibility="gone" 
     tools:text="Empty list text"/> 
</LinearLayout> 

ingredients_app_widget_info.xml

<?xml version="1.0" encoding="utf-8"?> 
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" 
        xmlns:tools="http://schemas.android.com/tools" 
        android:initialKeyguardLayout="@layout/ingredients_app_widget" 
        android:initialLayout="@layout/ingredients_app_widget" 
        android:minHeight="125dp" 
        android:minWidth="250dp" 
        android:previewImage="@mipmap/ic_launcher" 
        android:resizeMode="vertical" 
        android:updatePeriodMillis="86400000" 
        android:widgetCategory="home_screen" 
        android:configure="io.litebit.ilfornodellacasa.ui.activities.MainActivity" 
        tools:targetApi="jelly_bean_mr1"> 
</appwidget-provider> 

感谢您的帮助。

拉米比塔尔

在我看来,你是不是叫notifyAppWidgetViewDataChanged。 如果数据已更改,则需要通知窗口小部件集合视图需要更新。

AppWidgetManager.notifyAppWidgetViewDataChanged

此外,采取小工具的集合样品一看题目使用集合的应用小部件的 Keeping Collection Data Fresh

一个特点是,它能够 为用户提供先进的日期内容。例如,请考虑 Android 3.0 Gmail应用程序窗口小部件,该窗口小部件向用户提供其收件箱的快照 。为了使其成为可能,您需要能够触发您的RemoteViewsFactory和集合视图以获取并显示新的 数据。你与AppWidgetManager呼叫 notifyAppWidgetViewDataChanged()

+0

据我所知,我没有使用任何集合。无论如何,我添加了notifyAppWidgetViewDataChanged onDataSetChanged()方法,并没有发生任何事情。我用日志标记了主要方法,并且我可以看到服务没有启动,尽管它包含在清单中并且从AppWidgetProvider调用。 –

+0

您正在使用ListView,因此您正在使用集合.... 应该在updateAppWidget –

你需要让notifyAppWidgetViewDataChanged()updateAppWidget后()实现这一目标。 因此,使这样的逻辑:

appWidgetManager.updateAppWidget(widgetId, views); 
appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, R.id.lessons); // R.id.lessons - it's your listview id 
+0

之后添加notifyAppWidgetViewDataChanged。我想我在处理BroadcastReceiver的生命周期时做错了什么。 –