我对Android' RecyclerView.State提出了一个问题。
我正在使用RecyclerView,我如何使用它并将其与RecyclerView.State绑定?
我的目的是保存RecyclerView的滚动位置。
答案 0 :(得分:186)
如果您使用的是LinearLayoutManager,则会附带预先构建的save api linearLayoutManagerInstance.onSaveInstanceState()并恢复api linearLayoutManagerInstance.onRestoreInstanceState(...)
这样,您可以将返回的parcelable保存到outState。如,
outState.putParcelable("KeyForLayoutManagerState", linearLayoutManagerInstance.onSaveInstanceState());
,并使用您保存的状态恢复恢复位置。 e.g,
Parcelable state = savedInstanceState.getParcelable("KeyForLayoutManagerState");
linearLayoutManagerInstance.onRestoreInstanceState(state);
要包装所有内容,您的最终代码将类似于
private static final String BUNDLE_RECYCLER_LAYOUT = "classname.recycler.layout";
/**
* This is a method for Fragment.
* You can do the same in onCreate or onRestoreInstanceState
*/
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if(savedInstanceState != null)
{
Parcelable savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
recyclerView.getLayoutManager().onRestoreInstanceState(savedRecyclerLayoutState);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT, recyclerView.getLayoutManager().onSaveInstanceState());
}
编辑:您也可以使用与GridLayoutManager相同的api,因为它是LinearLayoutManager的子类。感谢@wegsehen的建议。
编辑:请记住,如果您还要在后台线程中加载数据,则需要在onPostExecute / onLoadFinished方法中调用onRestoreInstanceState,以便在方向更改时恢复位置,例如
@Override
protected void onPostExecute(ArrayList<Movie> movies) {
mLoadingIndicator.setVisibility(View.INVISIBLE);
if (movies != null) {
showMoviePosterDataView();
mDataAdapter.setMovies(movies);
mRecyclerView.getLayoutManager().onRestoreInstanceState(mSavedRecyclerLayoutState);
} else {
showErrorMessage();
}
}
答案 1 :(得分:72)
存储
lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
恢复
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);
如果这不起作用,请尝试
((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)
将商店放入onPause()
并在onResume()
答案 2 :(得分:32)
您打算如何使用RecyclerView.State
保存上次保存的位置?
你总是可以依靠ol&#39;好的保存状态。扩展RecyclerView
并覆盖onSaveInstanceState()和onRestoreInstanceState()
:
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
LayoutManager layoutManager = getLayoutManager();
if(layoutManager != null && layoutManager instanceof LinearLayoutManager){
mScrollPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
}
SavedState newState = new SavedState(superState);
newState.mScrollPosition = mScrollPosition;
return newState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
if(state != null && state instanceof SavedState){
mScrollPosition = ((SavedState) state).mScrollPosition;
LayoutManager layoutManager = getLayoutManager();
if(layoutManager != null){
int count = layoutManager.getItemCount();
if(mScrollPosition != RecyclerView.NO_POSITION && mScrollPosition < count){
layoutManager.scrollToPosition(mScrollPosition);
}
}
}
}
static class SavedState extends android.view.View.BaseSavedState {
public int mScrollPosition;
SavedState(Parcel in) {
super(in);
mScrollPosition = in.readInt();
}
SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mScrollPosition);
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
答案 3 :(得分:18)
我在onCreate()中设置变量,在onPause()中保存滚动位置,在onResume()中设置滚动位置
public static int index = -1;
public static int top = -1;
LinearLayoutManager mLayoutManager;
@Override
public void onCreate(Bundle savedInstanceState)
{
//Set Variables
super.onCreate(savedInstanceState);
cRecyclerView = ( RecyclerView )findViewById(R.id.conv_recycler);
mLayoutManager = new LinearLayoutManager(this);
cRecyclerView.setHasFixedSize(true);
cRecyclerView.setLayoutManager(mLayoutManager);
}
@Override
public void onPause()
{
super.onPause();
//read current recyclerview position
index = mLayoutManager.findFirstVisibleItemPosition();
View v = cRecyclerView.getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - cRecyclerView.getPaddingTop());
}
@Override
public void onResume()
{
super.onResume();
//set recyclerview position
if(index != -1)
{
mLayoutManager.scrollToPositionWithOffset( index, top);
}
}
答案 4 :(得分:17)
我希望在导航离开列表活动后再保存Recycler View的滚动位置,然后单击后退按钮进行导航。为此问题提供的许多解决方案要么比需要的要复杂得多,要么对我的配置不起作用,所以我想我会分享我的解决方案。
首先在onPause中保存您的实例状态,正如许多人所示。我认为值得强调的是,此版本的onSaveInstanceState是来自RecyclerView.LayoutManager类的方法。
private LinearLayoutManager mLayoutManager;
Parcelable state;
@Override
public void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
使其正常工作的关键是确保在连接适配器后调用onRestoreInstanceState,正如一些人在其他线程中指出的那样。然而,实际的方法调用比许多人指出的要简单得多。
private void someMethod() {
mVenueRecyclerView.setAdapter(mVenueAdapter);
mLayoutManager.onRestoreInstanceState(state);
}
答案 5 :(得分:11)
您不必再自己保存和恢复状态了。如果在xml和recyclerView.setSaveEnabled(true)中设置唯一ID,则默认情况下为true,系统将自动执行此操作。 以下是关于此的更多信息:http://trickyandroid.com/saving-android-view-state-correctly/
答案 6 :(得分:8)
从androidx recyclerView库的1.2.0-alpha02版本开始,现在可以对其进行自动管理。只需添加:
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
并使用:
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
StateRestorationPolicy枚举有3个选项:
请注意,在回答此问题时,recyclerView库仍处于alpha03中,但是 alpha阶段不适合用于生产目的。
答案 7 :(得分:3)
这是我保存它们并保持片段中recyclerview状态的方法。 首先,按照以下代码在您的父活动中添加配置更改。
android:configChanges="orientation|screenSize"
然后在您的Fragment中声明这些实例方法的
private Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;
在@onPause方法中添加以下代码
@Override
public void onPause() {
super.onPause();
//This used to store the state of recycler view
mBundleRecyclerViewState = new Bundle();
mListState =mTrailersRecyclerView.getLayoutManager().onSaveInstanceState();
mBundleRecyclerViewState.putParcelable(getResources().getString(R.string.recycler_scroll_position_key), mListState);
}
添加如下所示的onConfigartionChanged方法。
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//When orientation is changed then grid column count is also changed so get every time
Log.e(TAG, "onConfigurationChanged: " );
if (mBundleRecyclerViewState != null) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mListState = mBundleRecyclerViewState.getParcelable(getResources().getString(R.string.recycler_scroll_position_key));
mTrailersRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);
}
}, 50);
}
mTrailersRecyclerView.setLayoutManager(mLinearLayoutManager);
}
此方法将解决您的问题。 如果您有不同的布局管理器,则只需更换上部布局管理器即可。
答案 8 :(得分:3)
当您需要使用AsyncTaskLoader从Internet重新加载数据时,这是我在轮换后使用GridLayoutManager恢复RecyclerView位置的方法。
创建Parcelable和GridLayoutManager的全局变量以及静态最终字符串:
private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";
在onSaveInstance()
中保存gridLayoutManager的状态 @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_RECYCLER_LAYOUT,
mGridLayoutManager.onSaveInstanceState());
}
在onRestoreInstanceState中恢复
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//restore recycler view at same position
if (savedInstanceState != null) {
savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
}
}
然后,当加载程序从Internet获取数据时,您将恢复onLoadFinished()中的recyclelerview位置
if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}
..当然你必须在onCreate中实例化gridLayoutManager。 干杯
答案 9 :(得分:2)
支持库中捆绑的所有布局管理器已经知道如何保存和恢复滚动位置。
在满足以下条件时会在第一次布局遍历中恢复滚动位置:
RecyclerView
RecyclerView
通常,您以XML设置布局管理器,所以现在要做的就是
RecyclerView
您可以随时执行此操作,它不受限于Fragment.onCreateView
或Activity.onCreate
。
示例:确保每次更新数据时都已连接适配器。
viewModel.liveData.observe(this) {
// Load adapter.
adapter.data = it
if (list.adapter != adapter) {
// Only set the adapter if we didn't do it already.
list.adapter = adapter
}
}
保存的滚动位置是根据以下数据计算的:
因此,您可能会遇到轻微的不匹配,例如如果您的商品的纵向和横向尺寸不同。
答案 10 :(得分:1)
在Android API级别28上,我只需要确保通过LinearLayoutManager
方法设置RecyclerView.Adapter
和Fragment#onCreateView
以及所有Just Worked™️设置。我不需要做任何onSaveInstanceState
或onRestoreInstanceState
的工作。
Eugen Pechanec's answer解释了它为什么起作用。
答案 11 :(得分:1)
我有一个相同的问题,只是稍有不同,我正在使用数据绑定,并且我在绑定方法中设置了recyclerView适配器和layoutManager。
问题在于 onResume 之后发生了数据绑定,因此它在onResume之后重置了我的layoutManager,这意味着它每次都滚动回到原始位置。 因此,当我停止在数据绑定适配器方法中设置layoutManager时,它又能正常工作。
答案 12 :(得分:0)
对我来说,问题是我每次更换适配器时都会设置一个新的布局管理器,在recyclerView上丢失que滚动位置。
答案 13 :(得分:0)
我只想分享我在RecyclerView中遇到的困境。我希望遇到同样问题的任何人都会受益。
我的项目要求: 因此,我有一个RecyclerView,该列表列出了我的主活动(Activity-A)中的一些可单击项。单击“项目”后,将显示一个新的“活动”以及“项目详细信息”(活动B)。
我在清单文件中实现了Activity-A是Activity-B的父级,这样,我在Activity-B的ActionBar上具有后退或主页按钮>
问题: 每当我按下Activity-B ActionBar中的“后退”或“主页”按钮时,Activity-A就会进入完整的Activity生命周期,从onCreate()开始
即使我实现了保存RecyclerView适配器列表的onSaveInstanceState(),当Activity-A启动生命周期时,onCreate()方法中的saveInstanceState始终为null。
在Internet上进行进一步的挖掘时,我遇到了相同的问题,但该人注意到Anroid设备下方的“后退”或“主页”按钮(默认的“后退/主页”按钮),Activity-A并未进入“活动生命周期”
解决方案:
我在Activity-B上启用了主页或后退按钮
在onCreate()方法下添加以下行 supportActionBar?.setDisplayHomeAsUpEnabled(true)
在上面的有趣onOptionsItemSelected()方法中,我根据id检查了单击了哪个item的item.ItemId。后退按钮的ID为
android.R.id.home
然后在android.R.id.home内部实现finish()函数调用
这将结束活动B,并带来Acitivy-A,而无需经历整个生命周期。
根据我的要求,这是迄今为止最好的解决方案。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_show_project)
supportActionBar?.title = projectName
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when(item?.itemId){
android.R.id.home -> {
finish()
}
}
return super.onOptionsItemSelected(item)
}
答案 14 :(得分:0)
我有相同的要求,但是由于recyclerView的数据源,这里的解决方案并不能使我完全理解。
我正在onPause()中提取RecyclerViews的LinearLayoutManager状态
private Parcelable state;
Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}
可拆分的state
保存在onSaveInstanceState(Bundle outState)
中,并在onCreate(Bundle savedInstanceState)
时再次在savedInstanceState != null
中提取。
但是,recyclerView适配器是由ViewModel LiveData对象填充和更新的,该对象是DAO调用返回给Room数据库的ViewModel LiveData对象。
mViewModel.getAllDataToDisplay(mSelectedData).observe(this, new Observer<List<String>>() {
@Override
public void onChanged(@Nullable final List<String> displayStrings) {
mAdapter.swapData(displayStrings);
mLinearLayoutManager.onRestoreInstanceState(state);
}
});
我最终发现,在适配器中设置数据后立即恢复实例状态将使滚动状态保持旋转状态。
我怀疑这是因为数据库调用返回数据时我还原的LinearLayoutManager
状态已被覆盖,或者对于“空” LinearLayoutManager而言,还原的状态无意义。
如果适配器数据直接可用(即,在数据库调用中不连续),则可以在recyclerView上设置适配器后,在LinearLayoutManager上恢复实例状态。
两种情况之间的区别使我久违了。
答案 15 :(得分:0)
就我而言,我同时在XML和layoutManager
中设置了RecyclerView的onViewCreated
。删除onViewCreated
中的分配即可解决问题。
with(_binding.list) {
// layoutManager = LinearLayoutManager(context)
adapter = MyAdapter().apply {
listViewModel.data.observe(viewLifecycleOwner,
Observer {
it?.let { setItems(it) }
})
}
}
答案 16 :(得分:0)
您可以使用adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
中引入的recyclerview:1.2.0-alpha02
https://medium.com/androiddevelopers/restore-recyclerview-scroll-position-a8fbdc9a9334
但是它有一些问题,例如不能与内部RecyclerView一起使用,还有一些其他问题,您可以在中篇文章的评论部分中查看。
或者您可以将ViewModel
与SavedStateHandle
一起使用,这适用于内部RecyclerViews,屏幕旋转和进程死亡。
使用ViewModel
创建一个saveStateHandle
val scrollState=
savedStateHandle.getLiveData<Parcelable?>(KEY_LAYOUT_MANAGER_STATE)
使用Parcelable scrollState
来保存和恢复状态,如在其他帖子中所回答的那样,或者通过向RecyclerView添加滚动侦听器和
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
scrollState.value = mLayoutManager.onSaveInstanceState()
}
}
答案 17 :(得分:-1)
<强> Activity.java:强>
public RecyclerView.LayoutManager mLayoutManager;
Parcelable state;
mLayoutManager = new LinearLayoutManager(this);
// Inside `onCreate()` lifecycle method, put the below code :
if(state != null) {
mLayoutManager.onRestoreInstanceState(state);
}
@Override
protected void onResume() {
super.onResume();
if (state != null) {
mLayoutManager.onRestoreInstanceState(state);
}
}
@Override
protected void onPause() {
super.onPause();
state = mLayoutManager.onSaveInstanceState();
}
为什么我在OnSaveInstanceState()
中使用onPause()
意味着,在切换到另一个活动onPause时会调用它。当我们从另一个活动回来时,它将保存该滚动位置并恢复位置。 / p>