返回ListView时保持/保存/恢复滚动位置
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了返回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
的顶部定位。
不仅仅是ListView xml声明中的android:saveEnabled="true"
足够吗?
最佳解决方案是:
// 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);
}
});
你必须在邮件和线路上打电话!
如果在重新加载之前保存状态并在之后恢复,则可以在重新加载后保持滚动状态。在我的情况下,我做了一个异步网络请求,并在完成后在回调中重新加载列表。这是我恢复状态的地方。代码示例是Kotlin。
val state = myList.layoutManager.onSaveInstanceState()
getNewThings() { newThings: List<Thing> ->
myList.adapter.things = newThings
myList.layoutManager.onRestoreInstanceState(state)
}
如果您正在使用托管在活动上的片段,则可以执行以下操作:
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框架中已经实现的功能。除了一个警告之外,ListView
恢复精细的滚动位置:正如@aaronvargas所提到的,AbsListView
中有一个错误,它不会让第一个列表项恢复精细的滚动位置。然而,恢复滚动位置的最佳方法是不恢复它。 Android框架将为您做得更好。只要确保您符合以下条件:
- 确保你没有调用
setSaveEnabled(false)
方法,也没有为xml布局文件中的列表设置android:saveEnabled="false"
属性 - 对于
ExpandableListView
覆盖long getCombinedChildId(long groupId, long childId)
方法,以便它返回正长数(类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;
}
- 如果在片段中使用
ListView
或ExpandableListView
,则不会在活动重新创建时重新创建片段(例如,在屏幕旋转之后)。用findFragmentByTag(String tag)
方法获得片段。 - 确保
ListView
有一个android:id
,它是独一无二的。
为了避免上面提到的第一个列表项的警告你可以按照它返回位置0的ListView
的特殊虚拟零像素高度视图的方式来制作适配器。这是一个简单的示例项目显示ListView
和ExpandableListView
恢复它们的精细滚动位置,而它们的滚动位置是未明确保存/恢复。即使对于复杂的场景,临时切换到其他应用程序,双屏幕旋转和切换回测试应用程序,精细滚动位置也能完美恢复。请注意,如果您明确退出应用程序(通过按“返回”按钮),则不会保存滚动位置(以及所有其他视图也不会保存其状态)。 https://github.com/voromto/RestoreScrollPosition/releases
对于使用SimpleCursorAdapter实现LoaderManager.LoaderCallbacks的ListActivity派生的活动,它无法恢复onReset()中的位置,因为活动几乎总是重新启动,并且在关闭详细信息视图时重新加载适配器。诀窍是恢复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;
这里提供的解决方案似乎都不适合我。在我的情况下,我在ListView
中有一个Fragment
,我在FragmentTransaction
中替换,因此每次显示片段时都会创建一个新的Fragment
实例,这意味着ListView
状态不能存储为Fragment
的成员。
相反,我最终将状态存储在我的自定义Application
类中。下面的代码应该让您了解这是如何工作的:
public class MyApplication extends Application {
public static HashMap<String, Parcelable> parcelableCache = new HashMap<>();
/* ... code omitted for brevity ... */
}
public class MyFragment extends Fragment{
private ListView mListView = null;
private MyAdapter mAdapter = null;
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mAdapter = new MyAdapter(getActivity(), null, 0);
mListView = ((ListView) view.findViewById(R.id.myListView));
Parcelable listViewState = MyApplication.parcelableCache.get("my_listview_state");
if( listViewState != null )
mListView.onRestoreInstanceState(listViewState);
}
@Override
public void onPause() {
MyApplication.parcelableCache.put("my_listview_state", mListView.onSaveInstanceState());
super.onPause();
}
/* ... code omitted for brevity ... */
}
基本思想是将状态存储在片段实例之外。如果您不喜欢在应用程序类中使用静态字段的想法,我想您可以通过实现片段接口并将状态存储在您的活动中来实现。
另一个解决方案是将它存储在SharedPreferences
中,但它会变得有点复杂,你需要确保在应用程序启动时清除它,除非你希望在应用程序启动时保持状态。
此外,为了避免“当第一个项目可见时不保存滚动位置”,您可以显示具有0px
高度的虚拟第一个项目。这可以通过覆盖适配器中的getView()
来实现,如下所示:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if( position == 0 ) {
View zeroHeightView = new View(parent.getContext());
zeroHeightView.setLayoutParams(new ViewGroup.LayoutParams(0, 0));
return zeroHeightView;
}
else
return super.getView(position, convertView, parent);
}
我的答案是针对Firebase,位置0是一种解决方法
以上是关于返回ListView时保持/保存/恢复滚动位置的主要内容,如果未能解决你的问题,请参考以下文章 java [Android]返回ListView时保存滚动位置
java [Android]返回ListView 2时保存滚动位置
如何在应用重新启动之间保存和恢复 Flutter ListView 的滚动位置?
更新 SimpleCursorAdapter,同时保持 ListView 中的滚动位置