我有一个包含项目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
答案 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