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

问题描述:

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

+20

我认为尤金Mymrin /乔治Barchiesi提到的解决方案比接受的答案 – 2012-07-02 10:54:27

+0

由“乔治Barchiesi”工作对我给出的解决方案更好。其他解决方案不适合我。 – Bryan 2013-02-19 22:14:55

试试这个:

// 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的顶部开始定位其顶边的偏移量一起调用。

+2

不太了解这是如何工作的。我只使用listView.getFirstVisiblePosition(),并理解,但不知道这里发生了什么。任何简单解释的机会? :) – HXCaine 2010-09-01 22:25:43

+54

因此ListView.getFirstVisiblePosition()返回顶部可见列表项。但是这个项目可能会部分滚动到视图外,如果你想恢复列表的确切滚动位置,你需要获得这个偏移量。因此,ListView.getChildAt(0)返回顶部列表项的View,然后View.getTop()从ListView的顶部返回其相对偏移量。然后,为了恢复ListView的滚动位置,我们调用了ListView.setSelectionFromTop()和我们需要的项目的索引,以及一个偏移量,以便从ListView的顶部定位它的上边缘。全清? – ian 2010-09-02 07:17:22

+15

这可以更简单地使用一行来保存:'int index = mList.getFirstVisiblePosition();'并且只有一行可以恢复:'mList.setSelectionFromTop(index,0);'。虽然(+1)很好的答案!我一直在寻找一个优雅的解决方案来解决这个问题。 – Phil 2012-01-03 18:01:47

一个非常简单的方法:

/** Save the position **/ 
int currentPosition = listView.getFirstVisiblePosition(); 

//Here u should save the currentPosition anywhere 

/** Restore the previus saved position **/ 
listView.setSelection(savedPosition); 

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

一个更复杂的方法:

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); 
+2

您的答案中有错误。该方法被称为setSelection – Janusz 2010-06-10 14:10:32

+0

Ty为更正! – 2010-06-10 14:19:26

+0

这个想法效果很好,但是存在一个伪问题。第一个可见位置可能只显示一点点(可能只是该行的一小部分),setSelection()会将该行设置为在恢复时完全可见被制成。 – rantravee 2010-06-10 15:06:38

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

**我认为这是最好的答案**,因为此方法可以恢复确切的滚动位置,而不仅仅是第一个可见的项目。 – 2012-07-02 10:52:01

+0

是很好,但它不能很好地与旋转 – marekdef 2012-09-15 12:46:31

+9

很好的答案,但动态加载内容时,你想保存onPause()方法的状态不起作用。 – Gio 2012-09-18 12:47:41

我采用了@(柯克沃尔)建议的解决方案,它适用于我。我也在“联系人”应用的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设备上,当您更改手机方向以及编辑项目并返回列表时,此功能都可以使用。

+0

伟大的代码工作非常好,我的列表视图谢谢你这么much..really欣赏它 – user1106888 2013-02-06 22:57:18

+0

谢谢somuch它工作得很好..... – praveenb 2013-02-07 10:53:22

+1

该解决方案为我工作。其他人没有。如何保存/恢复ListView实例状态的行为与在ListView中删除和插入项目有关? – Bryan 2013-02-19 22:12:20

我发现了一些有趣的东西。

我试图为setSelection和scrolltoXY但它并没有在所有的工作,该列表保持在相同的位置,我得到了工作,经过一番试验和错误下面的代码,不会

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

如果相反的张贴可运行的你尝试runOnUiThread它也不起作用(至少在某些设备上)

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

+1

我遇到过同样的问题! 'setSelectionFromTop()'不起作用。 – ofavre 2012-02-03 23:29:14

+0

+1。 listnew.post()不适用于某些设备,在某些其他设备上,它在某些情况下不起作用,但runOnUIThread正常工作。 – 2012-11-22 09:21:20

+0

@Omar,你能举出一些设备和操作系统的例子吗?我尝试了一款HTC G2(2.3.4)和一款Galaxy Nexus(4.1.2),并且它们都可以使用。此线程中没有任何其他答案适用于我的任何设备。 – 2013-01-19 18:39:09

注意!如果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); 
+0

正确。我与我的大件物品有同样的问题 – passsy 2013-07-26 03:35:55

+0

非常感谢您的支持。 – tafi 2016-06-15 05:23:14

+0

它像一个魅力,但我不完全理解它。如果第一个项目仍然可见,那么获得下一个孩子有什么意义?不应该用新的索引setSelectionFromTop导致从第二个孩子开始显示列表?我怎么还看到第一个? – tafi 2016-06-15 05:34:22

张贴这是因为我很惊讶没有人提到过这一点。

用户单击后退按钮后,他将返回到列表视图,其状态与退出时的状态相同。

这段代码将覆盖“向上”按钮的行为方式与后退按钮的行为相同,因此在Listview - > Details - >返回列表视图(并且没有其他选项)的情况下,这是维护scrollposition和listview中的内容。

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

注意:如果你可以去到另一个活动从细节活性向上按钮即可返回回到了那个活动,所以你将不得不操纵后退按钮历史在为了这个工作。

是不是简单的android:saveEnabled="true"在ListView xml声明中就够了?

+1

android:saveEnabled默认设置为true。这没有做任何事情。 – Brad 2014-06-05 01:35:51

+0

没有。这还不够,你必须遵循http://stackoverflow.com/questions/3014089/maintain-save-restore-scroll-position-when-returning-to-a-listview?answertab=active#tab-top或http: //stackoverflow.com/questions/3014089/maintain-save-restore-scroll-position-when-returning-to-a-listview?answertab=active#answer-5688490 – 2014-10-02 23:07:26

如果您使用托管在一个活动的片段,你可以做这样的事情:

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

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

这足以

+1

添加更多详情请 – Rajesh 2015-02-27 04:31:51

+0

你好,请问你的代码上面的帮助我有一个RecyclerView列表,其中instancestate没有被保存在活动之间?我在这里发布了以下问题:http://stackoverflow.com/questions/35413495/android-how-do-i-return-to-newly-created-recyclerview-list? – AJW 2016-02-15 22:03:27

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

  • 确保你已经不叫setSaveEnabled(false)方法,而不是设置android:saveEnabled="false"为列表中的XML配置文件
  • 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位置 这里返回专用虚拟0像素高度视图为ListView是简单的示例项目显示ListViewExpandableListView恢复自己的精滚动位置,而他们的滚动位置不明确保存/恢复。即使对于临时切换到某些其他应用程序,双屏旋转和切换回测试应用程序的复杂场景,精细滚动位置也可以完美恢复。请注意,如果您正在显式退出应用程序(通过按“后退”按钮),则不会保存滚动位置(以及所有其他视图不会保存其状态)。 https://github.com/voromto/RestoreScrollPosition/releases

最好的办法是:

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

你必须拨打邮政与螺纹!

对于从使用SimpleCursorAdapter它没有工作,以恢复onReset()的位置实现LoaderManager.LoaderCallbacks ListActivity派生的活动,因为活动几乎总是重新和适配器被加载时的细节视图被关闭。诀窍是恢复在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的引用之后,我将适配器设置到了我的onCreateView中,并为我解决了这个问题。 =)

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

val state = myList.layoutManager.onSaveInstanceState() 

getNewThings() { newThings: List<Thing> -> 

    myList.adapter.things = newThings 
    myList.layoutManager.onRestoreInstanceState(state) 
}