在 android 片段中维护设备旋转时的列表项位置

Maintaining list items positions on device rotation in an android fragment

在一个片段中,我试图保存 RecyclerView 列表的滚动状态,但不知何故它没有保存状态。因为它是一个片段,所以我重写了 onSaveInstanceState() 和 onActivityCreated() 方法来保存滚动位置。甚至尝试在 onViewStateRestored() 方法中实现。我看到了一些关于保存滚动状态的相关帖子,但它不起作用。请让我知道我在哪里失败了。下面是我的代码:

public class RecipeListFragment extends Fragment
    implements RecipeListContract.View {
@BindView(R.id.recipe_list_recycler_view)
RecyclerView mRecipeListRecyclerView;
@BindView(R.id.recipe_list_progress_bar)
ProgressBar mRecipeListProgressBar;

@BindInt(R.integer.grid_column_count)
int mGridColumnCount;
@BindString(R.string.recipe_list_sync_completed)
String mRecipeListSyncCompleted;
@BindString(R.string.recipe_list_connection_error)
String mRecipeListConnectionError;

GridLayoutManager gridLayoutManager;
Parcelable savedRecyclerLayoutState;

Unbinder unbinder;

private static final String SAVED_LAYOUT_MANAGER
        = "com.learnwithme.buildapps.bakingapp.ui.recipelist.fragment";

private RecipeListContract.Presenter mRecipeListPresenter;
private RecipeListAdapter mRecipeListAdapter;

public RecipeListFragment() { }

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup 
container, @Nullable Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_recipe_list, container, 
false);
unbinder = ButterKnife.bind(this, view);

mRecipeListAdapter = new RecipeListAdapter(
            getContext(),
            new ArrayList<>(0),
            recipeId -> mRecipeListPresenter.loadRecipeDetails(recipeId)
);
mRecipeListAdapter.setHasStableIds(true);

    gridLayoutManager = new GridLayoutManager(getContext(), 
mGridColumnCount);
    mRecipeListRecyclerView.setLayoutManager(gridLayoutManager);
    mRecipeListRecyclerView.setHasFixedSize(true);
    mRecipeListRecyclerView.setAdapter(mRecipeListAdapter);

    return view;
}

@Override
public void onPause() {
    super.onPause();
    mRecipeListPresenter.unsubscribe();
}

@Override
public void onResume() {
    super.onResume();
    mRecipeListPresenter.subscribe();
}

@Override
public void onSaveInstanceState() { }

@Override
public void onSaveInstanceState(Bundle bundle) {
    super.onSaveInstanceState(bundle);
    if(bundle != null) {
        bundle.putParcelable(SAVED_LAYOUT_MANAGER,
                mRecipeListRecyclerView
                    .getLayoutManager()
                    .onSaveInstanceState());
        Timber.d("instance state=>", 
mRecipeListRecyclerView.getLayoutManager().onSaveInstanceState());
    }
}

@Override
public void onViewStateRestored(@Nullable Bundle bundle) {
    super.onViewStateRestored(bundle);
    if(bundle != null) {
        savedRecyclerLayoutState = 
bundle.getParcelable(SAVED_LAYOUT_MANAGER);
        Timber.d("onViewStateRestored savedRecyclerLayoutState=>", 
savedRecyclerLayoutState);
        mRecipeListRecyclerView
                .getLayoutManager()
                .onRestoreInstanceState(savedRecyclerLayoutState);
    }
}

@Override
public void onActivityCreated(@Nullable Bundle bundle) {
    super.onActivityCreated(bundle);
    if(bundle != null) {
        savedRecyclerLayoutState = 
bundle.getParcelable(SAVED_LAYOUT_MANAGER);
        Timber.d("onViewStateRestored savedRecyclerLayoutState=>",
                savedRecyclerLayoutState);
        mRecipeListRecyclerView
                .getLayoutManager()
                .onRestoreInstanceState(savedRecyclerLayoutState);
    }
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    unbinder.unbind();
}

public static RecipeListFragment newInstance() {
    return new RecipeListFragment();
}

@Override
public void setPresenter(RecipeListContract.Presenter recipeListPresenter) {
    this.mRecipeListPresenter = recipeListPresenter;
}

@Override
public void showRecipeList(List<Recipe> recipeList) {
    mRecipeListAdapter.refreshRecipes(recipeList);
}

@Override
public void loadProgressBar(boolean show) {
    setViewVisibility(mRecipeListRecyclerView, !show);
    setViewVisibility(mRecipeListProgressBar, show);
}

@Override
public void displayCompletedMessage() {
    Toast.makeText(getContext(), mRecipeListSyncCompleted, 
Toast.LENGTH_SHORT).show();
}

@Override
public void displayErrorMessage() {
    Toast.makeText(getContext(), mRecipeListConnectionError, 
Toast.LENGTH_SHORT).show();
}

@Override
public void displayRecipeDetails(int recipeId) {
    startActivity(RecipeDetailsActivity.prepareIntent(getContext(), 
recipeId));
}

private void setViewVisibility(View view, boolean visible) {
    if (visible) {
        view.setVisibility(View.VISIBLE);
    } else {
        view.setVisibility(View.INVISIBLE);
    }
}
}

我已经自己解决了这个问题。问题是在 onSaveInstanceState() 中保存设备的滚动位置,并在 onViewRestored() 方法中恢复它。

private Parcelable mRecipeListParcelable;
private int mScrollPosition = -1;

@Override
public void onSaveInstanceState(Bundle bundle) {
    super.onSaveInstanceState(bundle);
    int scrollPosition = ((GridLayoutManager) 
    mRecipeListRecyclerView.getLayoutManager())
            .findFirstCompletelyVisibleItemPosition();
    mRecipeListParcelable = gridLayoutManager.onSaveInstanceState();
    bundle.putParcelable(KEY_LAYOUT, mRecipeListParcelable);
    bundle.putInt(POSITION, scrollPosition);
}

@Override
public void onViewStateRestored(@Nullable Bundle bundle) {
    super.onViewStateRestored(bundle);
    if(bundle != null) {
        mRecipeListParcelable = bundle.getParcelable(KEY_LAYOUT);
        mScrollPosition = bundle.getInt(POSITION);
    }
}

此外,在 loadProgress() 方法中,我必须设置 scrollToPosition() 并保存滚动位置。

@Override
public void loadProgressBar(boolean show) {
    setViewVisibility(mRecipeListRecyclerView, !show);
    setViewVisibility(mRecipeListProgressBar, show);
    mRecipeListRecyclerView.scrollToPosition(mScrollPosition);
}

另外,要记住的另一件事是,无需在 onResume() 方法中恢复任何内容,因为演示者回调将被调用并且视图将被重置。