从其他活动返回到同一片段时,如何保存和恢复片段中 RecyclerView 的滚动位置?

Posted

技术标签:

【中文标题】从其他活动返回到同一片段时,如何保存和恢复片段中 RecyclerView 的滚动位置?【英文标题】:How to save and restore scrolling position of the RecyclerView in a fragment when coming back from other activity to the same fragment? 【发布时间】:2019-03-06 08:45:07 【问题描述】:

对版主的要求:这不是重复的问题,请阅读以下信息。

在问这个问题之前,我尝试了几乎所有可用的 SO 解决方案,但没有一个对我有用,有些导致崩溃,有些根本不起作用(我是初学者,有可能我在我的代码中做错了什么,我没有责怪任何人在 SO 上提供的答案在我的情况下不起作用)。以下是我的代码,请看一下:

fragment_tab1.xml(包含recyclerview):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_
    android:layout_
    android:background="#333333"
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <RelativeLayout
    android:layout_
    android:layout_
    android:layout_marginTop="96dp">

        <LinearLayout
            android:layout_
            android:layout_
            android:background="@color/colorPrimaryDark"

            android:id="@+id/sort1"
            android:orientation="horizontal">
        <TextView
            android:layout_
            android:layout_
            android:id="@+id/sort"
            android:layout_marginLeft="10dp"
            android:fontFamily="@font/quicksand"
            android:textStyle="bold"
            android:text="Sort by:"
            android:textColor="#ffffff"/>

            <Button
                android:layout_
                android:layout_
                android:layout_marginLeft="10dp"
                style="@style/Widget.AppCompat.Button.Colored"
                android:text="Oldest first"/>
            <Button
            android:layout_
            android:layout_
                style="@style/Widget.AppCompat.Button.Colored"
            android:text="Newest first"/>
        </LinearLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_below="@id/sort1"
        android:clipToPadding="false"
        android:layout_
        android:layout_
        android:layout_marginBottom="10dp"
        android:padding="5dp" />

    </RelativeLayout>

</LinearLayout>

Fragmenttab1.java

public class tab1  extends Fragment 
    RecyclerView mRecyclerView;
    FirebaseDatabase mFirebaseDatabase;
    DatabaseReference mRef;
    LinearLayoutManager manager;
    private static final String TAG = "tab1";
    ProgressDialog progressDialog;
    View rootView;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 

        //Returning the layout file after inflating
        //Change R.layout.tab1 in you classes
         rootView = inflater.inflate(R.layout.fragment_tab1, container, false);


return rootView;

    


    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) 
        super.onActivityCreated(savedInstanceState);
        mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);



        manager = new LinearLayoutManager(getActivity());
        mRecyclerView.setLayoutManager(manager);
        mRecyclerView.setItemViewCacheSize(20);
        mRecyclerView.setDrawingCacheEnabled(true);
        mFirebaseDatabase = FirebaseDatabase.getInstance();
        mRef = mFirebaseDatabase.getReference("memes");

        mFirebaseDatabase.getInstance().getReference().keepSynced(true);

        progressDialog = new ProgressDialog(getActivity());
        progressDialog.setMessage("Please Wait");
        progressDialog.show();

    

    public void onStart() 

        super.onStart();


        FirebaseRecyclerAdapter<Model, ViewHolder> firebaseRecyclerAdapter =
                new FirebaseRecyclerAdapter<Model, ViewHolder>(
                        Model.class,
                        R.layout.row2,
                        ViewHolder.class,
                        mRef

                ) 



                    @Override
                    protected void populateViewHolder(ViewHolder viewHolder, Model model, int position) 
                        viewHolder.setDetails(getActivity(), model.getTitle(), model.getDesc(), model.getImage());
                        progressDialog.cancel();
                    



                    @Override
                    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 

                        ViewHolder viewHolder = super.onCreateViewHolder(parent, viewType);

                        viewHolder.setOnClickListener(new ViewHolder.ClickListener()




                            @Override
                            public void onItemClick(View view, int position)



                                //get data from firebase here
                                String mTitle = getItem(position).getTitle();
                                String mDesc= getItem(position).getDesc();
                                String mImage = getItem(position).getImage();

                                //pass this data to new activity
                                Intent intent = new Intent(view.getContext(), ArticleDetail.class);
                                intent.putExtra("title", mTitle);// put title
                                intent.putExtra("desc", mDesc);// put description
                                intent.putExtra("image", mImage); //put image urls

                                startActivity(intent);
                            




                            @Override
                            public void onItemLongClick(View view, int position)

                                //TODO your own implementation on long item click


                            

                        );

                        return viewHolder;
                    
                ;


        mRecyclerView.setAdapter(firebaseRecyclerAdapter);

    

    @Override
    public void  onPause() 
        super.onPause();
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());

        View firstChild = mRecyclerView.getChildAt(0);
        int firstVisiblePosition = mRecyclerView.getChildAdapterPosition(firstChild);
        int offset = firstChild.getTop();

        Log.d(TAG, "Postition: " + firstVisiblePosition);
        Log.d(TAG, "Offset: " + offset);

        preferences.edit()
                .putInt("position", firstVisiblePosition)
                .putInt("offset", offset)
                .apply();
    


    @Override
    public void onResume()
        super.onResume();

    
    @Override
    public void onSaveInstanceState(Bundle outState) 


        super.onSaveInstanceState(outState);
    

    @Override
    public void onViewStateRestored(@Nullable Bundle savedInstanceState) 


        super.onViewStateRestored(savedInstanceState);
    


【问题讨论】:

看这里:***.com/a/65645664/3904109 【参考方案1】:

花了这么多天,尝试了很多解决方案,终于找到了解决我问题的有效解决方案,感谢https://***.com/a/42563804/2128364

我已将工作解决方案移至其他方法以使其在片段中工作。

也许这可以节省某人的一天,这是它的工作原理:

    onConfigurationChanged(按照原方案的建议),onSaveInstanceState 或 onViewRestoredState 方法不起作用,因此您需要 onPause 和 onResume 方法分别保存和恢复状态。 您必须在 onPause 方法中保存状态:
mBundleRecyclerViewState = new Bundle();    
mListState = mRecyclerView.getLayoutManager().onSaveInstanceState();
mBundleRecyclerViewState.putParcelable(KEY_RECYCLER_STATE, mListState);
    然后在onResume方法中恢复:
if (mBundleRecyclerViewState != null) 
    new Handler().postDelayed(new Runnable() 
        @Override
        public void run() 
            mListState = mBundleRecyclerViewState.getParcelable(KEY_RECYCLER_STATE);
            mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);
        
    , 50);
    
mRecyclerView.setLayoutManager(staggeredGridLayoutManager);

【讨论】:

这拯救了我的一天! mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);正如您提到的,应该在处理程序内部。它就像魅力一样。谢谢! 你是我的救星,我已经经历了一个月,但没有找到答案,我真的很感激 你能告诉我 delayMilles 50) 是什么意思吗? do in onResume 我的意思是它到底在做什么【参考方案2】:

如果您还没有,请在您的 build.gradle 中添加 recyclerView 依赖项

implementation "androidx.recyclerview:recyclerview:1.2.0"

不仅仅是在初始化列表适配器之后设置stateRestorationPolicy

 listAdapter = ListAdapter()

 // Set scroll restore policy
 listdapter.stateRestorationPolicy =
      RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY

有关stateRestorationPolicy 和不同类型政策visit this article 的更多信息

【讨论】:

那时我没有使用 Androidx。我也有解决方案,但我也会尝试你的解决方案,如果可行,我会回复你。谢谢。 你把这段代码放在哪里?我正在为我的 recyclerview 使用线性布局,并且我已经放置了我的代码 (myAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY); 就在我声明适配器并且它不工作之后。我什至在 onResume 中尝试过它,它不会不行。 适配器中是否有任何默认项目,例如标题或加载进度指示器? 这是用于屏幕旋转,而不是用于在恢复时存储RecyclerView 滚动位置。【参考方案3】:

你应该使用这个方法来返回第一个可见的项目:

int findFirstVisibleItemPosition();

那么你必须在 onPause() 中保存这个索引,当用户返回时在 onResume() 中检索并设置它:

void scrollToPosition (int position)

【讨论】:

感谢您的回复,我做到了,但它不起作用:(,我是这样做的: public void onPause() super.onPause(); firstVisibleItem = ((LinearLayoutManager) mRecyclerView.getLayoutManager()).findFirstVisibleItemPosition(); @Override public void onResume() super.onResume(); ((LinearLayoutManager)mRecyclerView.getLayoutManager()).scrollToPosition(firstVisibleItem); 在滚动到最后一个位置之前,您是否用最后一个数据填充了适配器? 这个我不知道,你能指导我吗? 假设您首先加载 10 个项目,当用户关闭时,您再次加载 10 个项目。您现在有 20 个项目,第一个可见项目的索引可能是 12。当用户返回时,您加载 10 个项目,但最后一个位置是 12,因为您没有加载 10 个下一个项目。 我的意思是你把它保存在哪里?共享首选项?【参考方案4】:

问题是作用域,当您移动到另一个活动或片段时,此片段中的变量会被破坏

【讨论】:

【参考方案5】:

如果有人介意它的接受答案使用静态对象 (static Bundle mBundleRecyclerViewState) 来保持状态,您可以改用ViewModel

public class MainViewModel extends ViewModel 
    private Parcelable mRecyclerViewState;
    
    /**
     * Back up RecyclerView's state to ViewModel in Fragment's onDestroyView
     * or in Activity's onStop (or in somewhere else appropriate)
     *
     * @param layoutManager RecyclerView.LayoutManager object
     */
    public void saveRecyclerViewState(RecyclerView.LayoutManager layoutManager) 
        mRecyclerViewState = layoutManager.onSaveInstanceState();
    

    /**
     * Restore backed up RecyclerView's state from ViewModel in onCreate
     *
     * @param layoutManager RecyclerView.LayoutManager object
     */
    public void restoreRecyclerViewState(RecyclerView.LayoutManager layoutManager) 
        layoutManager.onRestoreInstanceState(mRecyclerViewState);
    

用法示例(在 Fragment 中):

private MainViewModel mViewModel;
private GridLayoutManager mLayoutManager;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);

    mViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);

    mLayoutManager = new GridLayoutManager(requireContext(), ITEMS_PER_ROW);
    mViewModel.restoreRecyclerViewState(mLayoutManager);


@Override
public void onDestroyView() 
    mViewModel.saveRecyclerViewState(mLayoutManager);

    super.onDestroyView();

【讨论】:

以上是关于从其他活动返回到同一片段时,如何保存和恢复片段中 RecyclerView 的滚动位置?的主要内容,如果未能解决你的问题,请参考以下文章

Android阻止恢复活动

如何从片段返回主要活动

恢复片段而不重新创建?

从另一个活动返回后如何恢复活动中最后打开的片段

保存以编程方式为片段创建的视图并在 onresume 中恢复

从后台堆栈恢复片段时的 savedInstanceState