我有一个很长的ListView,用户可以在返回前一个屏幕之前滚动它。当用户再次打开这个ListView时,我希望列表被滚动到与之前相同的位置。关于如何实现这一点,你有什么想法吗?


当前回答

我使用的是FirebaseListAdapter,不能让任何解决方案工作。我最后做了这个。我猜有更优雅的方式,但这是一个完整的和有效的解决方案。

在onCreate之前:

private int reset;
private int top;
private int index;

FirebaseListAdapter内部:

@Override
public void onDataChanged() {
     super.onDataChanged();

     // Only do this on first change, when starting
     // activity or coming back to it.
     if(reset == 0) {
          mListView.setSelectionFromTop(index, top);
          reset++;
     }

 }

启动时间:

@Override
protected void onStart() {
    super.onStart();
    if(adapter != null) {
        adapter.startListening();
        index = 0;
        top = 0;
        // Get position from SharedPrefs
        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        top = sharedPref.getInt("TOP_POSITION", 0);
        index = sharedPref.getInt("INDEX_POSITION", 0);
        // Set reset to 0 to allow change to last position
        reset = 0;
    }
}

停止:

@Override
protected void onStop() {
    super.onStop();
    if(adapter != null) {
        adapter.stopListening();
        // Set position
        index = mListView.getFirstVisiblePosition();
        View v = mListView.getChildAt(0);
        top = (v == null) ? 0 : (v.getTop() - mListView.getPaddingTop());
        // Save position to SharedPrefs
        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        sharedPref.edit().putInt("TOP_POSITION" + "", top).apply();
        sharedPref.edit().putInt("INDEX_POSITION" + "", index).apply();
    }
}

因为我还必须解决这个FirebaseRecyclerAdapter,我在这里发布的解决方案:

在onCreate之前:

private int reset;
private int top;
private int index;

FirebaseRecyclerAdapter内部:

@Override
public void onDataChanged() {
    // Only do this on first change, when starting
    // activity or coming back to it.
    if(reset == 0) {
        linearLayoutManager.scrollToPositionWithOffset(index, top);
        reset++;
    }
}

启动时间:

@Override
protected void onStart() {
    super.onStart();
    if(adapter != null) {
        adapter.startListening();
        index = 0;
        top = 0;
        // Get position from SharedPrefs
        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        top = sharedPref.getInt("TOP_POSITION", 0);
        index = sharedPref.getInt("INDEX_POSITION", 0);
        // Set reset to 0 to allow change to last position
        reset = 0;
    }
}

停止:

@Override
protected void onStop() {
    super.onStop();
    if(adapter != null) {
        adapter.stopListening();
        // Set position
        index = linearLayoutManager.findFirstVisibleItemPosition();
        View v = linearLayoutManager.getChildAt(0);
        top = (v == null) ? 0 : (v.getTop() - linearLayoutManager.getPaddingTop());
        // Save position to SharedPrefs
        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        sharedPref.edit().putInt("TOP_POSITION" + "", top).apply();
        sharedPref.edit().putInt("INDEX_POSITION" + "", index).apply();
    }
}

其他回答

If you are saving/restoring scroll position of ListView yourself you are essentially duplicating the functionality already implemented in android framework. The ListView restores fine scroll position just well on its own except one caveat: as @aaronvargas mentioned there is a bug in AbsListView that won't let to restore fine scroll position for the first list item. Nevertheless the best way to restore scroll position is not to restore it. Android framework will do it better for you. Just make sure you have met the following conditions:

确保你没有调用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标签)方法获取片段。 确保ListView有一个唯一的android:id。

To avoid aforementioned caveat with first list item you can craft your adapter the way it returns special dummy zero pixels height view for the ListView at position 0. Here is the simple example project shows ListView and ExpandableListView restore their fine scroll positions whereas their scroll positions are not explicitly saved/restored. Fine scroll position is restored perfectly even for the complex scenarios with temporary switching to some other application, double screen rotation and switching back to the test application. Please note, if you are explicitly exiting the application (by pressing the Back button) the scroll position won't be saved (as well as all other Views won't save their state). https://github.com/voromto/RestoreScrollPosition/releases

用于从实现LoaderManager的ListActivity派生的活动。LoaderCallbacks使用SimpleCursorAdapter,它不能恢复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;
Parcelable state;

@Override
public void onPause() {    
    // Save ListView state @ onPause
    Log.d(TAG, "saving listview state");
    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);
    }
}

难道不是简单的android:saveEnabled="true"在ListView xml声明足够吗?

使用下面的代码:

int index,top;

@Override
protected void onPause() {
    super.onPause();
    index = mList.getFirstVisiblePosition();

    View v = challengeList.getChildAt(0);
    top = (v == null) ? 0 : (v.getTop() - mList.getPaddingTop());
}

无论何时你刷新你的数据使用下面的代码:

adapter.notifyDataSetChanged();
mList.setSelectionFromTop(index, top);