如何保存回收视图的滚动位置使用回收视图。状态?

我有一个关于 Android 的 回收站,州立大学的问题。

我正在使用回收视图,我如何使用和绑定它与回收视图。国家?

我的目标是 保存回收视图的滚动位置

157068 次浏览

商店

lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();

恢复

((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);

如果没用,试试看

((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)

把存储在 onPause() 恢复到 onResume()

你打算如何保存上次保存的位置与 RecyclerView.State

你总是可以依赖 ol’good 的保存状态。扩展 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 在下面的回答中分享了更多的细节

旧答案

如果您正在使用 LinearLayoutManager,它提供了预构建的保存 api OnSaveInstanceState ()和还原 api OnRestoreInstanceState (...)

这样,您就可以保存退回的包裹到您的郊区,

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());
}

编辑: 你也可以在 GridLayoutManager 中使用相同的 api,因为它是 LinearLayoutManager 的一个子类。谢谢@wegsehen 的建议。

编辑: 记住,如果您也在后台线程中加载数据,那么您需要在 onPostExecute/onLoadFinish 方法中调用 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();
}
}

我在 onCreate ()中设置变量,在 onPuse ()中保存滚动位置,在 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);
}
}

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();


}

为什么我在 onPause()中使用 OnSaveInstanceState()意味着,当切换到另一个活动时,暂停将被调用。它将保存滚动位置,并在我们从另一个活动返回时恢复位置。

你再也不用独自拯救和重建国家了。如果您在 xml 中设置惟一 ID,并且回收 View.setSaveEnable (true)(默认为 true)系统将自动执行此操作。 这里有更多关于这方面的内容: < a href = “ http://trickyandroid.com/save-android-view-state-wrong/”rel = “ norefrer”> http://trickyandroid.com/saving-android-view-state-correctly/

我想保存回收视图的滚动位置时,导航远离我的列表活动,然后点击后退按钮导航回来。针对这个问题提供的许多解决方案要么比需要的复杂得多,要么不适合我的配置,所以我想我应该分享我的解决方案。

首先将您的实例状态保存在 onPuse 中,如许多示例所示。我认为这里值得强调的是,这个版本的 onSaveInstanceState 是一个来自回收视图的方法。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);
}

对于我来说,问题在于每次更改适配器时,我都会设置一个新的 layoutmanager,从而失去了在回收视图上滚动的位置。

这是我如何恢复回收视图的位置与 GridLayoutManager 旋转后,当您需要重新加载数据从互联网与 AsyncTaskLoader。

创建一个 Parcelable 和 GridLayoutManager 的全局变量和一个静态的 final 字符串:

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);
}
}

然后,当加载程序从因特网获取数据时,在 onLoadFinish ()中恢复回收视图位置

if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}

. . 当然你必须在 onCreate 中实例化 gridLayoutManager。 干杯

这里是我的方法来保存它们,并维持在一个片段的回收视图的状态。 首先,在父活动中添加配置更改,如下面的代码所示。

android:configChanges="orientation|screenSize"

然后在片段中声明这些实例方法的

private  Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;

在@onPuse 方法中添加以下代码

@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);
}

这种方法将重新解决您的问题。 如果你有不同的布局管理器,那么只要替换上层布局管理器。

我只想分享我最近在回收视图中遇到的困境。我希望任何遇到同样问题的人都能从中受益。

我的专题需求: 因此,我有一个回收视图,列出了一些可点击的项目在我的主要活动(活动-A)。当单击该项目时,将显示一个带有项目详细信息(Activity-B)的新活动。

我在 Manifest 文件中实现了 Activity-A 是 Activity-B 的父级,这样,我就在 Activity-B 的 ActionBar 上有了一个 back 或 home 按钮

问题: 每次我在 Activity-B ActionBar 中按下 Back 或 Home 按钮,Activity-A 就会从 onCreate ()开始进入完整的 Activity 生命周期

即使我实现了一个 onSaveInstanceState ()来保存回收视图的适配器列表,当 Activity-A 启动它的生命周期时,在 onCreate ()方法中,saveInstanceState 始终为 null。

在进一步挖掘互联网时,我遇到了同样的问题,但是那个人注意到在 Anroid 设备下面的返回或主页按钮(默认返回/主页按钮) ,Activity-A 没有进入 Activity Life-Cycle。

解决方案:

  1. 我删除了活动 -B 的清单中的父活动标签
  2. 我在 Activity-B 上启用了 home 或 back 按钮

    在 onCreate ()方法下添加以下行 < strong > support ActionBar? . setDisplayHomeAsUpEnable (true)

  3. 在 overide fun onOptionsItemSelected ()方法中,我检查了该项。根据 id 单击项目的 ItemId。后面按钮的 ID 是

    机器人 Rid 回家

    然后在 android.R.id.home 中实现 Finish ()函数调用

    这将结束活动 -B,并在不经历整个生命周期的情况下带来活动 -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)
}

我也有同样的需求,但是这里的解决方案并没有让我完全满足,因为它是回收视图的数据源。

我正在 onPuse ()中提取回收视图的 LinearLayoutManager 状态

private Parcelable state;


Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}

可分割的 state保存在 onSaveInstanceState(Bundle outState)中,当 savedInstanceState != null时在 onCreate(Bundle savedInstanceState)中再次提取。

但是,回收视图适配器是由对 Room 数据库的 DAO 调用返回的 ViewModelLiveData 对象填充和更新的。

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状态在数据库调用返回数据时被覆盖了,要么是因为恢复的状态对于“ em pty”LinearLayoutManager 没有意义。

如果适配器数据是直接可用的(即不依赖于数据库调用) ,那么在回收视图上设置适配器之后,就可以在 LinearLayoutManager 上恢复实例状态。

这两种情况之间的区别使我困惑了很长时间。

在 Android API Level 28上,我只是确保在 Fragment#onCreateView方法中设置了 LinearLayoutManagerRecyclerView.Adapter,以及 Just WorkedTM 中的所有内容。我不需要做任何 onSaveInstanceStateonRestoreInstanceState的工作。

Eugen Pechecc 的回答解释了为什么这样做。

更新: 自从 1.2.0-alpha02版本以来,有一个新的 API 来控制何时发生状态恢复(包括滚动位置)。

延迟状态恢复:

向回收视图添加了新的 API。适配器类,该类允许适配器控制何时还原布局状态。

例如,你可以致电:

myAdapter.setStateRestorationStrategy(StateRestorationStrategy.WHEN_NOT_EMPTY);

使回收视图等待,直到适配器不是空的,然后恢复滚动位置。

参见:


所有捆绑在支持库中的布局管理器都已经知道如何保存和恢复滚动位置。

RecyclerView/ScrollView/随便什么需要有一个 android:id来保存它的状态。

默认情况下,滚动位置在第一次布局传递时恢复,当满足下列条件时发生:

  • 布局管理器连接到 RecyclerView
  • 一个适配器连接到 RecyclerView

通常用 XML 设置布局管理器,因此现在需要做的就是

  1. 用数据加载适配器
  2. 然后 将适配器连接到 RecyclerView

你可以在任何时候这样做,它不限于 Fragment.onCreateViewActivity.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
}
}

保存的滚动位置根据以下数据计算:

  • 屏幕上第一项的适配器位置
  • 项目顶部与列表顶部的像素偏移量(用于垂直布局)

因此,你可以预期一个轻微的不匹配,例如,如果你的项目有不同的尺寸在纵向和横向方向。

我在使用 数据绑定时遇到了同样的问题,只有一个小小的不同,我在绑定方法中设置了回收视图适配器和 layoutManager。

问题是数据绑定是在 继续之后发生的,所以它在 onResume 之后重置了 layoutManager,这意味着它每次都会滚动回原来的位置。 因此,当我停止在 Databinding 适配器方法中设置 layoutManager 时,它又可以正常工作了。

在我的例子中,我使用 XML 和 onViewCreated来设置回收视图的 layoutManager。删除 onViewCreated中的赋值修复了它。

with(_binding.list) {
//  layoutManager =  LinearLayoutManager(context)
adapter = MyAdapter().apply {
listViewModel.data.observe(viewLifecycleOwner,
Observer {
it?.let { setItems(it) }
})
}
}

从安卓回收视图库的1.2.0-alpha02版本开始,它现在是自动管理的:

implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"

使用:

adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY

StateRestorationPolicy 枚举有3个选项:

  • ALLOW ー默认状态,在下一个布局传递中立即恢复回收视图状态
  • PREVENT _ When _ EMPTY ー仅当适配器不是空的时候才恢复回收视图状态(Adapter.getItemCount () > 0)。如果数据是异步加载的,回收视图将等待数据加载完成,然后才还原状态。如果在适配器中有默认项,如头或加载进度指示器,那么应该使用 PREVENT 选项,除非使用 合并适配器添加默认项。MergeAdapter 等待所有适配器准备就绪,然后才恢复状态。
  • 预防ー所有状态恢复都将延迟,直到设置 ALLOW 或 PREVENT _ When _ EMPTY 为止。

注意,在回答这个问题时,回收视图库仍然是 alpha03,但是是 阿尔法阶段不适合生产目的

您可以使用 adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY,它是在 recyclerview:1.2.0-alpha02中引入的

Https://medium.com/androiddevelopers/restore-recyclerview-scroll-position-a8fbdc9a9334

但它有一些问题,如不能与内部的回收视图,以及其他一些问题,你可以检查在中后期的评论部分。

或者你可以使用 ViewModelSavedStateHandle,它可以用于内部回收视图,屏幕旋转和 接受死亡

saveStateHandle创建一个 ViewModel

val scrollState=
savedStateHandle.getLiveData<Parcelable?>(KEY_LAYOUT_MANAGER_STATE)

使用 ParcelablescrollState保存和恢复状态,如在其他文章中所回答的,或通过添加一个滚动侦听器,以回收视图和

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()
}
}

由于我发现大多数选项都太长或太复杂,这里是我的短期 科特林选项与 ViewBindingViewModel实现这一点:

如果您希望了解回收视图的状态生命周期,请将此代码放入 ViewModel 中,否则您可以使用基本的 Repository 并从那里开始工作:

private lateinit var state: Parcelable
fun saveRecyclerViewState(parcelable: Parcelable) { state = parcelable }
fun restoreRecyclerViewState() : Parcelable = state
fun stateInitialized() : Boolean = ::state.isInitialized

还有这个在你的 暂停里面

binding.recyclerView.layoutManager?.onSaveInstanceState()?.let { viewModel.saveRecyclerViewState(it) }

最后是你的 简历()

if (viewModel.stateInitialized()) {
binding.recyclerView.layoutManager?.onRestoreInstanceState(
viewModel.restoreRecyclerViewState()
)
}

享受:)

另一种方式 看这个,

首先,定义变量,

private LinearLayoutManager mLayoutManager;
int lastPosition;


public static final String MyPREFERENCES__DISPLAY = "MyPrefs_display" ;
SharedPreferences sharedpreferences_display;
SharedPreferences.Editor editor_display;

那么,

mLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(mLayoutManager);


recyclerView.scrollToPosition(sharedpreferences_display.getInt("last_saved_position", 0));


recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
lastPosition = mLayoutManager.findFirstVisibleItemPosition();
}
});

如果要按按钮保存位置,

btn.setOnClickListener(v -> {
editor_display.putInt("last_saved_position", lastPosition).apply();
Toast.makeText(Arama_Fav_Activity.this, "Saved", Toast.LENGTH_SHORT).show();
});