2010-06-10 3 views
359

У меня есть длинный ListView, который пользователь может прокрутить, прежде чем вернуться к предыдущему экрану. Когда пользователь снова откроет этот ListView, я хочу, чтобы список был прокручен до той же точки, что и раньше. Любые идеи о том, как достичь этого?Поддержание/сохранение/восстановление позиции прокрутки при возврате в ListView

+20

Я думаю, что решения, упомянутые Евгений Мымрин/Giorgio Barchiesi лучше, чем принято отвечать –

+0

Приведенное решение по «Giorgio Barchiesi» работал для меня. Другие решения не помогли мне. – Bryan

ответ

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

+54

Итак, ListView.getFirstVisiblePosition() возвращает элемент верхнего видимого списка. Но этот элемент может быть частично прокручен вне поля зрения, и если вы хотите восстановить точную позицию прокрутки списка, вам нужно получить это смещение. Таким образом ListView.getChildAt (0) возвращает представление для элемента верхнего списка, а затем View.getTop() возвращает относительный смещение в верхней части ListView. Затем, чтобы восстановить положение прокрутки ListView, мы вызываем ListView.setSelectionFromTop() с индексом нужного элемента и смещением, чтобы расположить его верхний край от вершины ListView. Все чисто? – ian

+15

Это можно сделать еще проще, используя одну строку для сохранения: 'int index = mList.getFirstVisiblePosition();' и только одна строка для восстановления: 'mList.setSelectionFromTop (index, 0);'. Отличный ответ, хотя (+1)! Я искал элегантное решение этой проблемы. – Phil

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

+0

Ty для исправления! –

+0

Идея работает хорошо, но есть псевдопроблема. Первая видимая позиция может быть показана только немного (может быть, только небольшая часть строки), а setSelection() задает эту строку полностью видимой при восстановлении сделан. – rantravee

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

** Я думаю, что это лучший ответ **, поскольку этот метод восстанавливает точную позицию прокрутки, а не только первый видимый элемент. –

+0

Да, отлично, но с вращением не работает – marekdef

+9

Отличный ответ, но не будет работать при динамической загрузке содержимого и вы хотите сохранить состояние в методе onPause(). – Gio

51

Я принял решение, предложенное @ (Kirk Woll), и оно работает для меня. Я также видел в исходном коде 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

Отличный код работал очень хорошо с моим списком взглядов, спасибо вам большое ... очень ценю это – user1106888

+0

Спасибо, что он работал нормально ..... – praveenb

+1

Это решение сработало для меня. Другие - нет. Как происходит сохранение/восстановление состояния экземпляра ListView в отношении удаления и вставки элементов в ListView? – Bryan

17

Я нашел в этом что-то интересное.

Я попытался setSelection и scrolltoXY, но он не работает вообще, список оставался в том же положении, после некоторых проб и ошибок, я получил следующий код, который делает работу

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

Если вместо размещения Runnable вы попробуете runOnUiThread, он тоже не работает (по крайней мере, на некоторых устройствах)

Это очень странное обходное решение для чего-то, что должно быть прямолинейным.

+1

У меня возникла такая же проблема! И 'setSelectionFromTop()' не работает. – ofavre

+0

+1. listnew.post(), не работает на некоторых устройствах, а на некоторых других устройствах он не работает в определенных случаях, но runOnUIThread работает нормально. –

+0

@ Omar, можете ли вы привести некоторые примеры устройств и ОС, на которых это не работает? Я пробовал HTC G2 (2.3.4) и Galaxy Nexus (4.1.2), и он работал над обоими. Ни один из других ответов в этом потоке не работал ни на одном из моих устройств. –

10

ВНИМАНИЕ! В AbsListView есть ошибка, которая не позволяет корректной работе onSaveState(), если ListView.getFirstVisiblePosition() равно 0.

Так что если у вас есть большие изображения, которые занимают большую часть экрана, и вы перечисляете на второе изображение, но немного из первого показывают, позиция прокрутки не будет сохранена ...

от 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

+0

Большое вам спасибо за это. – tafi

+0

Это работает как шарм, но я не совсем понимаю его. Если первый элемент все еще виден, как это имеет смысл, чтобы получить следующего ребенка? Не следует ли setSelectionFromTop с новым индексом вызывать отображение списка, начиная со второго ребенка? Как я все еще вижу первый? – tafi

0

Опубликовать это, потому что я удивлен, что никто не упомянул об этом.

После того, как пользователь нажмет кнопку «Назад», он вернется в список в том же состоянии, в каком он вышел.

Этот код будет переопределять кнопку «вверх», чтобы вести себя так же, как кнопка «Назад», поэтому в случае Listview -> Details -> Back to Listview (и других параметров) это простейший код для поддержки прокрутки и содержимого в списке.

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

Внимание: Если вы можете перейти на другой вид деятельности от деталей деятельности до кнопки вернет вас к этой деятельности, так что вы должны будете манипулировать историей BackButton для того, чтобы это работало.

1

Не просто android:saveEnabled="true" в описании ListView xml достаточно?

+1

android: saveEnabled по умолчанию установлено значение true. Это ничего не делает. – Brad

+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 –

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

Добавить более подробную информацию pls – Rajesh

+0

Привет, будет ли ваш код выше help меня с списком RecyclerView, где instancestate не сохраняется между действиями? Я разместил здесь следующий вопрос: http://stackoverflow.com/questions/35413495/android-how-do-return-to-newly-created-recyclerview-list? – AJW

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; 
} 
  • , если ListView или ExpandableListView используется в фрагменте не воссоздать фрагмент на активности отдыха (после поворота экрана, например).Получите фрагмент с помощью метода findFragmentByTag(String tag).
  • удостоверьтесь, что у ListView есть android:id и он уникален.

Чтобы избежать вышеупомянутого нюанса с первым элементом списка вы можете изготовить адаптер, как он возвращает специальный фиктивный ноль пикселей вида высоты для ListView в положении 0. Вот простой пример проект показывает ListView и ExpandableListView восстановить их штраф пока их позиции прокрутки не будут явно сохранены/восстановлены. Точная позиция прокрутки восстанавливается отлично даже для сложных сценариев с временным переключением на другое приложение, двойным поворотом экрана и переключением обратно в тестовое приложение. Обратите внимание: если вы явно выходите из приложения (нажав кнопку «Назад»), положение прокрутки не будет сохранено (так же как все остальные виды не сохранят свое состояние). https://github.com/voromto/RestoreScrollPosition/releases

0

ЛУЧШЕЕ РЕШЕНИЕ IS:

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

Для деятельности, полученной из ListActivity, который реализует LoaderManager.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; 
3

Для некоторых, кто ищет решение этой проблемы, корень проблемы может быть в том месте, где вы устанавливаете адаптер списка видов. После того, как вы установите адаптер в списке, он сбрасывает положение прокрутки. Просто подумать. Я переместил адаптер в мой onCreateView после того, как мы захватили ссылку на список, и он решил проблему для меня. =)

1

Вы можете сохранить состояние прокрутки после перезагрузки, если вы сохраните состояние до перезагрузки и восстановления после него. В моем случае я сделал асинхронный сетевой запрос и перезагрузил список в обратном вызове после его завершения. Здесь я восстанавливаю состояние. Образец кода - это Котлин.

val state = myList.layoutManager.onSaveInstanceState() 

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

    myList.adapter.things = newThings 
    myList.layoutManager.onRestoreInstanceState(state) 
} 
Смежные вопросы