ListView未在Android Widget中更新

时间:2017-09-08 18:04:28

标签: android listview widget remoteview

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

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

提前感谢您的帮助。

Ramy Bittar

2 个答案:

答案 0 :(得分:0)

在我看来,你没有打电话给notifyAppWidgetViewDataChanged。 如果数据已更改,则需要通知窗口小部件需要更新集合视图。

AppWidgetManager.notifyAppWidgetViewDataChanged

另外,请查看主题小部件的集合示例 Keeping Collection Data Fresh

  

使用集合的app小部件的一个功能是能够   为用户提供最新内容。例如,考虑一下   Android 3.0 Gmail应用小部件,为用户提供快照   他们的收件箱。为了实现这一点,您需要能够触发   您的RemoteViewsFactory和集合视图用于获取和显示新的   数据。您可以通过AppWidgetManager调用实现此目的   的 notifyAppWidgetViewDataChanged ()

答案 1 :(得分:0)

您需要在updateAppWidget()之后生成notifyAppWidgetViewDataChanged()。 所以制作这样的逻辑:

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