返回到ListView时维护/保存/恢复滚动位置

我有一个很长的ListView ,用户可以在返回到前一个屏幕前滚动。 当用户再次打开这个ListView时,我希望列表滚动到与之前相同的点。 任何想法如何实现这一目标?

尝试这个:

 // save index and top position int index = mList.getFirstVisiblePosition(); View v = mList.getChildAt(0); int top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop()); // ... // restore index and position mList.setSelectionFromTop(index, top); 

说明:

ListView.getFirstVisiblePosition()返回顶部可见列表项。 但是这个项目可能会部分滚动查看,如果你想恢复列表的确切滚动位置,你需要得到这个偏移量。 因此, ListView.getChildAt(0)返回顶部列表项的View ,然后View.getTop() - mList.getPaddingTop()ListView的顶部返回其相对偏移量。 然后,为了恢复ListView的滚动位置,我们调用了ListView.setSelectionFromTop()和我们想要的项目的索引,以及从ListView的顶部定位其顶部边缘的偏移量。

 Parcelable state; @Override public void onPause() { // Save ListView state @ onPause Log.d(TAG, "saving listview state @ onPause"); state = listView.onSaveInstanceState(); super.onPause(); } ... @Override public void onViewCreated(final View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // Set new items listView.setAdapter(adapter); ... // Restore previous state (including selected item index and scroll position) if(state != null) { Log.d(TAG, "trying to restore listview state.."); listView.onRestoreInstanceState(state); } } 

我采用了@(Kirk Woll)build议的解决scheme,它适用于我。 我也在“联系人”应用程序的Android源代码中看到,他们使用类似的技术。 我想添加一些更多的细节:在我的ListActivity派生类顶部:

 private static final String LIST_STATE = "listState"; private Parcelable mListState = null; 

然后,一些方法覆盖:

 @Override protected void onRestoreInstanceState(Bundle state) { super.onRestoreInstanceState(state); mListState = state.getParcelable(LIST_STATE); } @Override protected void onResume() { super.onResume(); loadData(); if (mListState != null) getListView().onRestoreInstanceState(mListState); mListState = null; } @Override protected void onSaveInstanceState(Bundle state) { super.onSaveInstanceState(state); mListState = getListView().onSaveInstanceState(); state.putParcelable(LIST_STATE, mListState); } 

当然,“loadData”是我从数据库检索数据并将其放到列表中的函数。

在我的Froyo设备上,当您更改手机方向,以及编辑某个项目并返回列表时,这两项function都可以使用。

一个非常简单的方法:

 /** Save the position **/ int currentPosition = listView.getFirstVisiblePosition(); //Here u should save the currentPosition anywhere /** Restore the previus saved position **/ listView.setSelection(savedPosition); 

setSelection方法将列表重置为提供的项目。 如果不处于触摸模式,则在触摸模式下,该项目实际上将被select,该项目将仅被定位在屏幕上。

更复杂的方法:

 listView.setOnScrollListener(this); //Implements the interface: @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mCurrentX = view.getScrollX(); mCurrentY = view.getScrollY(); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } //Save anywere the x and the y /** Restore: **/ listView.scrollTo(savedX, savedY); 

我发现一些有趣的事情。

我尝试setSelection和scrolltoXY,但它根本不工作,列表保持在相同的位置,经过一些试验和错误,我得到了下面的代码,可以工作

 final ListView list = (ListView) findViewById(R.id.list); list.post(new Runnable() { @Override public void run() { list.setSelection(0); } }); 

如果不是发布Runnable,而是运行runOnUiThread,它也不起作用(至less在某些设备上)

这是一个非常奇怪的解决方法,应该是直截了当的事情。

警告!! 如果ListView.getFirstVisiblePosition()为0,则AbsListView中存在一个错误,它不允许onSaveState()正常工作。

所以,如果你有大的图像,占据了大部分的屏幕,而你滚动到第二个图像,但有一个第一个显示,滚动的位置将不会被保存…

AbsListView.java:1650 (评论我的)

 // this will be false when the firstPosition IS 0 if (haveChildren && mFirstPosition > 0) { ... } else { ss.viewTop = 0; ss.firstId = INVALID_POSITION; ss.position = 0; } 

但在这种情况下,下面的代码中的“顶部”将是一个负数,这会导致其他问题阻止状态被正确恢复。 所以当“顶”是消极的,得到下一个孩子

 // save index and top position int index = getFirstVisiblePosition(); View v = getChildAt(0); int top = (v == null) ? 0 : v.getTop(); if (top < 0 && getChildAt(1) != null) { index++; v = getChildAt(1); top = v.getTop(); } // parcel the index and top // when restoring, unparcel index and top listView.setSelectionFromTop(index, top); 
 private Parcelable state; @Override public void onPause() { state = mAlbumListView.onSaveInstanceState(); super.onPause(); } @Override public void onResume() { super.onResume(); if (getAdapter() != null) { mAlbumListView.setAdapter(getAdapter()); if (state != null){ mAlbumListView.requestFocus(); mAlbumListView.onRestoreInstanceState(state); } } } 

这就够了

对于一些寻找解决这个问题的方法,问题的根源可能在于你设置你的列表视图适配器。 在列表视图上设置适配器后,它将重置滚动位置。 只是要考虑一下。 在我们获取对listview的引用之后,我将适配器设置到我的onCreateView中,并且为我解决了这个问题。 =)

是不是简单的android:saveEnabled="true"在ListView的XML声明不够?

重新加载后,如果在重新加载并保存之前保存状态,则可以保持滚动状态。 在我的情况下,我做了一个asynchronousnetworking请求,并在完成后重新加载callback列表。 这是我恢复状态的地方。 代码示例是Kotlin。

 val state = myList.layoutManager.onSaveInstanceState() getNewThings() { newThings: List<Thing> -> myList.adapter.things = newThings myList.layoutManager.onRestoreInstanceState(state) } 

张贴这个,因为我很惊讶没有人提到过这个。

用户点击后退button后,他会返回到列表视图,在他离开它的状态。

这段代码将覆盖“向上”button,以与后退button相同的方式进行操作,所以在Listview – > Details – > Back to Listview(并且没有其他选项)的情况下,这是维护滚动位置和内容的最简单的代码在列表视图中。

  public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: onBackPressed(); return(true); } return(super.onOptionsItemSelected(item)); } 

警告:如果您可以从细节活动中转到其他活动,则向上button会将您返回到该活动,因此您必须操作后退历史logging才能使其工作。

如果你正在使用一个活动的片段,你可以这样做:

 public abstract class BaseFragment extends Fragment { private boolean mSaveView = false; private SoftReference<View> mViewReference; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (mSaveView) { if (mViewReference != null) { final View savedView = mViewReference.get(); if (savedView != null) { if (savedView.getParent() != null) { ((ViewGroup) savedView.getParent()).removeView(savedView); return savedView; } } } } final View view = inflater.inflate(getFragmentResource(), container, false); mViewReference = new SoftReference<View>(view); return view; } protected void setSaveView(boolean value) { mSaveView = value; } } public class MyFragment extends BaseFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { setSaveView(true); final View view = super.onCreateView(inflater, container, savedInstanceState); ListView placesList = (ListView) view.findViewById(R.id.places_list); if (placesList.getAdapter() == null) { placesList.setAdapter(createAdapter()); } } } 

如果你自己保存/恢复ListView滚动位置,你基本上复制了已经在android框架中实现的function。 除了一个警告之外, ListView可以很好地恢复精细的滚动位置:@aaronvargas提到AbsListView中有一个错误,它不会让第一个列表项恢复精细的滚动位置。 不过,恢复滚动位置的最好方法不是恢复它。 Android框架将为您做得更好。 只要确保您符合以下条件:

  • 确保你没有调用setSaveEnabled(false)方法,并且没有为xml布局文件中的列表设置android:saveEnabled="false"属性
  • 对于ExpandableListView覆盖long getCombinedChildId(long groupId, long childId)方法,以便返回正数long number( BaseExpandableListAdapter默认实现返回负数)。 这里是例子:

 @Override public long getChildId(int groupPosition, int childPosition) { return 0L | groupPosition << 12 | childPosition; } @Override public long getCombinedChildId(long groupId, long childId) { return groupId << 32 | childId << 1 | 1; } @Override public long getGroupId(int groupPosition) { return groupPosition; } @Override public long getCombinedGroupId(long groupId) { return (groupId & 0x7FFFFFFF) << 32; } 
  • 如果在片段中使用ListViewExpandableListView则不要在活动重新创build(例如,屏幕旋转后)上重新创build片段。 用findFragmentByTag(String tag)方法获取片段。
  • 确保ListView有一个android:id ,它是唯一的。

为了避免前面提到的第一个列表项的问题,你可以使用适配器来返回特殊的虚拟零像素高度的ListView位置0的视图。下面是简单的示例项目显示ListViewExpandableListView恢复其精细的滚动位置,而他们的滚动位置是没有明确保存/恢复。 即使对于临时切换到其他应用程序,双屏旋转和切换回testing应用程序的复杂场景,精细滚动位置也可以完美恢复。 请注意,如果您正在退出应用程序(通过按“后退”button),滚动位置将不会被保存(以及所有其他视图不会保存其状态)。 https://github.com/voromto/RestoreScrollPosition/releases

最佳解决scheme是:

 // save index and top position int index = mList.getFirstVisiblePosition(); View v = mList.getChildAt(0); int top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop()); // ... // restore index and position mList.post(new Runnable() { @Override public void run() { mList.setSelectionFromTop(index, top); } }); 

你必须打电话在线和线索!

对于从ListActivity派生的使用SimpleCursorAdapter实现LoaderManager.LoaderCallbacks的活动,无法恢复onReset()中的位置,因为活动几乎总是重新启动,并且在详细信息视图closures时重新加载适配器。 诀窍是恢复onLoadFinished()中的位置:

在onListItemClick()中:

 // save the selected item position when an item was clicked // to open the details index = getListView().getFirstVisiblePosition(); View v = getListView().getChildAt(0); top = (v == null) ? 0 : (v.getTop() - getListView().getPaddingTop()); 

在onLoadFinished()中:

 // restore the selected item which was saved on item click // when details are closed and list is shown again getListView().setSelectionFromTop(index, top); 

在onBackPressed()中:

 // Show the top item at next start of the app index = 0; top = 0;