返回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恢复精细的滚动位置:正如@a​​aronvargas所提到的,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;
}
  • 如果在片段中使用ListViewExpandableListView,则不会在活动重新创建时重新创建片段(例如,在屏幕旋转之后)。用findFragmentByTag(String tag)方法获得片段。
  • 确保ListView有一个android:id,它是独一无二的。

为了避免上面提到的第一个列表项的警告你可以按照它返回位置0的ListView的特殊虚拟零像素高度视图的方式来制作适配器。这是一个简单的示例项目显示ListViewExpandableListView恢复它们的精细滚动位置,而它们的滚动位置是未明确保存/恢复。即使对于复杂的场景,临时切换到其他应用程序,双屏幕旋转和切换回测试应用程序,精细滚动位置也能完美恢复。请注意,如果您明确退出应用程序(通过按“返回”按钮),则不会保存滚动位置(以及所有其他视图也不会保存其状态)。 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 中的滚动位置

从其他活动返回到同一片段时,如何保存和恢复片段中 RecyclerView 的滚动位置?

在顶部添加新项目时在 ListView 中保持滚动位置