항목의 ListView가있는 위젯이 있습니다. 위젯이 새롭고 비어있을 때 사용자는이를 클릭하여 객체 목록 (각 객체는 항목 목록을 포함 함)으로 활동을로드하고 객체를 선택하고 위젯은이를 수신하고 내용을 업데이트하여 표시해야합니다. 개체 (목록 포함)가 AppWidgetProvider에 의해 수신되고 업데이트가 호출되는 단계에 도달했습니다. 내가하지 못한 것은 공급자가 RemoteViewService와 더 많은 단계를 호출하도록하는 것이다. 검토를 위해 클래스와 XML을 포함하겠습니다.안드로이드 위젯에서 ListView가 업데이트되지 않습니다.
의 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
0,123,516package 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>
012,351,
미리 도움을 주셔서 감사합니다.
Ramy Bittar은
알고있는 한, 나는 어떤 수집도 사용하고 있지 않다. 어쨌든, notifyAppWidgetViewDataChanged를 onDataSetChanged() 메소드에 추가했는데 아무 일도 없었습니다. 로그로 주요 메소드를 표시했고, 매니페스트에 포함되어 있고 AppWidgetProvider에서 호출되었지만 서비스가 시작되지 않고 있음을 알 수 있습니다. –
컬렉션을 사용하기 위해 ListView를 사용하고 있습니다 ... updateAppWidget 뒤에 notifyAppWidgetViewDataChanged를 추가해야합니다. –