我对Android的RecyclerView.State有疑问。
我正在使用RecyclerView,如何使用RecyclerView.State并将其与RecyclerView.State绑定?
我的目的是保存RecyclerView的滚动位置。
我对Android的RecyclerView.State有疑问。
我正在使用RecyclerView,如何使用RecyclerView.State并将其与RecyclerView.State绑定?
我的目的是保存RecyclerView的滚动位置。
Answers:
您打算如何保存RecyclerView.State
?
您始终可以依靠OL的良好保存状态。扩展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];
}
};
}
从recyclerview:1.2.0-alpha02
发行版StateRestorationPolicy
开始进行了介绍。对于给定的问题,这可能是一种更好的方法。
这个主题已经在android开发人员中篇文章中讨论过。
此外,@rubén-viguera在下面的答案中分享了更多详细信息。https://stackoverflow.com/a/61609823/892500
如果您使用的是LinearLayoutManager,则它带有预构建的保存api linearLayoutManagerInstance.onSaveInstanceState()和还原api linearLayoutManagerInstance.onRestoreInstanceState(...)
这样,您可以将返回的包裹保存到outState中。例如,
outState.putParcelable("KeyForLayoutManagerState", linearLayoutManagerInstance.onSaveInstanceState());
,并以您保存的状态还原还原位置。例如,
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());
}
编辑:您还可以将相同的api与GridLayoutManager一起使用,因为它是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();
}
}
RecyclerView
但如果每个页面上都有一个ViewPager
,则不会RecycerView
。
onCreateView
,但是要在数据加载之后进行。参见@Farmaker答案。
商店
lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
恢复
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);
如果这不起作用,请尝试
((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)
放入商店onPause()
并还原onResume()
lastFirstVisiblePosition
,以0
恢复它之后。当您有一个片段/活动将一个不同的数据加载到一个RecyclerView
文件(例如文件浏览器应用程序)中时,这种情况很有用。
SwipeRefreshLayout
怎么办?
RecyclerView
当我离开列表活动,然后单击“后退”按钮后退时,我想保存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);
}
我在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);
}
}
您不再需要自己保存和恢复状态。如果您在xml中设置了唯一ID,然后recyclerView.setSaveEnabled(true)(默认为true),系统将自动执行。这是有关此的更多信息:http : //trickyandroid.com/saving-android-view-state-correctly/
支持库中捆绑的所有布局管理器已经知道如何保存和恢复滚动位置。
当满足以下条件时,滚动位置将在第一次布局遍历中恢复:
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
}
}
根据以下数据计算保存的滚动位置:
因此,您可以预期会出现轻微的不匹配,例如,如果您的商品在纵向和横向方向上具有不同的尺寸。
在RecyclerView
/ ScrollView
/任何需要有一个android:id
对自己的状态进行保存。
LinearLayoutManager.SavedState
:cs.android.com/androidx/platform/frameworks/support/+/…
这是我保存它们并维护片段中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);
}
此方法将解决您的问题。如果您有不同的布局管理器,则只需更换上部布局管理器。
当您需要使用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()中恢复recyclerview位置
if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}
..当然,您必须在onCreate内实例化gridLayoutManager。干杯
我也有相同的要求,但是由于recyclerView的数据源,这里的解决方案并不能使我完全理解。
我在onPause()中提取了RecyclerViews的LinearLayoutManager状态
private Parcelable state;
Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}
当时,将Parcelable state
保存在中onSaveInstanceState(Bundle outState)
,然后再次提取。onCreate(Bundle savedInstanceState)
savedInstanceState != null
但是,recyclerView适配器由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上还原实例状态。
两种情况之间的区别使我久违了。
在Android API级别28上,我只需要确保按照自己的方法设置了LinearLayoutManager
和方法,并设置了Just Justed™️的所有内容。我不需要做任何事情或工作。RecyclerView.Adapter
Fragment#onCreateView
onSaveInstanceState
onRestoreInstanceState
Eugen Pechanec的答案解释了为什么这样做。
从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相不适用于生产目的。
对我来说,问题在于每次更改适配器时,我都会设置一个新的layoutmanager,从而失去了recyclerView上的滚动位置。
我只想分享我在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并未进入“活动生命周期”。
解:
我在活动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)
}
我有一个相同的问题,只是稍有不同,我正在使用Databinding,并且在绑定方法中设置了recyclerView适配器和layoutManager。
问题在于onResume之后发生了数据绑定,因此它在onResume之后重置了我的layoutManager,这意味着它每次都滚动回到原始位置。因此,当我停止在数据绑定适配器方法中设置layoutManager时,它又可以正常工作。
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()
in onPause()
方式,会在切换到另一个活动时调用onPause。它将保存该滚动位置并在我们从另一个活动返回时恢复该位置。