2010-06-10 72 views
359

我有很長的ListView,用戶可以在返回到前一個屏幕前滾動。當用戶再次打開此ListView時,我希望列表滾動到與之前相同的點。任何想法如何實現這一目標?返回ListView時維護/保存/恢復滾動位置

+20

我認爲尤金Mymrin /喬治Barchiesi提到的解決方案比接受的答案 – 2012-07-02 10:54:27

+0

由「喬治Barchiesi」工作對我給出的解決方案更好。其他解決方案不適合我。 – Bryan 2013-02-19 22:14:55

回答

612

試試這個:

// 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

27

一個非常簡單的方法:

/** 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

498
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

51

我採用了@(柯克沃爾)建議的解決方案,它適用於我。我也在「聯繫人」應用的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

17

我發現了一些有趣的東西。

我試圖爲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

10

注意!如果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

0

張貼這是因爲我很驚訝沒有人提到過這一點。

用戶單擊後退按鈕後,他將返回到列表視圖,其狀態與退出時的狀態相同。

這段代碼將覆蓋「向上」按鈕的行爲方式與後退按鈕的行爲相同,因此在Listview - > Details - >返回列表視圖(並且沒有其他選項)的情況下,這是維護scrollposition和listview中的內容。

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

注意:如果你可以去到另一個活動從細節活性向上按鈕即可返回回到了那個活動,所以你將不得不操縱後退按鈕歷史在爲了這個工作。

1

是不是簡單的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

0

如果您使用託管在一個活動的片段,你可以做這樣的事情:

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

0

如果要保存/自己恢復的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

0

最好的辦法是:

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

你必須撥打郵政與螺紋!

0

對於從使用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; 
3

對於一些尋找解決此問題的方法,問題的根源可能是您設置列表視圖適配器的位置。在列表視圖上設置適配器後,它將重置滾動位置。只是需要考慮。在我們獲取對listview的引用之後,我將適配器設置到了我的onCreateView中,併爲我解決了這個問題。 =)

1

如果在重新加載之前保存狀態並在之後進行恢復,則可以在重新加載後保持滾動狀態。在我的情況下,我做了一個異步網絡請求,並在完成後在回調中重新加載列表。這是我恢復狀態的地方。代碼示例是Kotlin。

val state = myList.layoutManager.onSaveInstanceState() 

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

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