Сохранить / сохранить / восстановить позицию прокрутки при возврате в ListView

397

Я долго, ListViewчто пользователь может прокручивать перед возвратом к предыдущему экрану. Когда пользователь откроет это ListViewснова, я хочу, чтобы список был прокручен до той же точки, что и ранее. Есть идеи как этого добиться?

rantravee
источник
22
Я думаю, что решения, упомянутые Eugene Mymrin / Giorgio Barchiesi, лучше, чем принятый ответ
Mira Weller

Ответы:

631

Попробуй это:

// 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
56
Поэтому ListView.getFirstVisiblePosition () возвращает наиболее видимый элемент списка. Но этот элемент может быть частично прокручен вне поля зрения, и если вы хотите восстановить точную позицию прокрутки списка, вам нужно получить это смещение. Поэтому ListView.getChildAt (0) возвращает представление для верхнего элемента списка, а затем View.getTop () возвращает его относительное смещение от вершины ListView. Затем, чтобы восстановить позицию прокрутки ListView, мы вызываем ListView.setSelectionFromTop () с индексом элемента, который мы хотим, и смещением для позиционирования его верхнего края от вершины ListView. Все чисто?
Иан
15
Это может быть сделано еще проще использовать одну строку , чтобы сохранить: int index = mList.getFirstVisiblePosition();и только одна линия для восстановления: mList.setSelectionFromTop(index, 0);. Отличный ответ (+1)! Я искал элегантное решение этой проблемы.
Фил
17
@Phil ваше упрощенное решение не работает, чтобы восстановить точное положение прокрутки.
Ixx
2
как сказал @nbarraille, код должен выглядеть как «v.getTop () - mList.getPaddingTop ()». В противном случае вы потратите час, как я, пытаясь понять, почему он всегда восстанавливает волосы ...
jdowdell,
544
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);
    }
}
Евгений Мымрин
источник
106
Я думаю, что это лучший ответ, так как этот метод восстанавливает точное положение прокрутки, а не только первый видимый элемент.
Мира Уэллер
9
Хороший ответ, но он не будет работать при динамической загрузке контента, и вы хотите сохранить состояние в методе onPause ().
Gio
13
отличное решение. Только одна проблема. Если элементы большие, и вы прокручиваете, но первый элемент все еще виден, список переходит наверх. если вы прокрутите до второго пункта или где-нибудь еще, все работает
passsy
4
@passsy Вы когда-нибудь находили решение в списке, возвращающемся к вершине?
Papajohn000
2
Как сказал aaronvargas, это не может работать, когда ListView.getFirstVisiblePosition () равен 0.
VinceStyling
54

Я принял решение, предложенное @ (Кирк Волл), и оно работает для меня. Я также видел в исходном коде 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 это работает как при изменении ориентации телефона, так и при редактировании элемента и возвращении к списку.

Джорджио Барчиези
источник
1
Это решение сработало для меня. Другие не сделали. Как происходит сохранение / восстановление состояния экземпляра ListView в отношении удаления и вставки элементов в ListView?
Брайан
2
К вашему сведению, если вы используете это во фрагменте (я использую фрагмент списка) и переходите к другим фрагментам, это приведет к сбою, поскольку представление содержимого не создается при вызове в mListState = getListView().onSaveInstanceState().основном при ротации устройства.
Mgamerz
2
onRestoreInstanceStateникогда не называется :(
dVaffection
1
@GiorgioBarchiesi Кто такой @ (Kirk Wool), чье решение подходит вам? Пользователь явно изменил свое имя.
Пиовезан,
1
Да, это официальная религия. Но в моем случае данные загружаются локально, имеют очень ограниченную длину и загружаются за доли секунды, поэтому я пошёл на эрекции :-)
Джорджио Барчиези
26

Очень простой способ:

/** 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
Идея работает хорошо, но есть псевдо проблема. Первая видимая позиция может быть показана лишь немного, (возможно, только небольшая часть строки), и setSelection () устанавливает эту строку, чтобы быть полностью видимой, когда восстановление сделали.
rantravee
Пожалуйста, посмотрите на мой второй вариант. Я только что добавил его в свой первый ответ
Франческо Лаурита
27
view.getScrollX () и view.getScrollY () всегда возвращают 0!
Rantravee
3
Взгляните на мое (принятое) решение выше, используя View.getChildAt () и View.getTop (). Вы не можете использовать View.getScrollY () для ListView, потому что ListView поддерживает его внутреннюю прокрутку.
Иан
17

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

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

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

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

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

shalafi
источник
1
Я испытал ту же проблему! И setSelectionFromTop()не работает.
2012 г.
+1. listnew.post (), не работает на некоторых устройствах, а на некоторых других устройствах он не работает в определенных случаях, но runOnUIThread работает нормально.
Омар Рехман
@ Омар, можешь привести примеры устройств и ОС, на которых это не работает? Я попробовал HTC G2 (2.3.4) и Galaxy Nexus (4.1.2), и он работал на обоих. Ни один из других ответов в этой теме не работал ни для одного из моих устройств.
Дан Джей
2
listview.post отлично работает на моем Galaxy Note с 4.0.4, но не работает на моем Nexus One с 2.3.6. Однако onOnUIThread (action) отлично работает на обоих устройствах.
Омар Рехман
11

ВНИМАНИЕ !! В 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);
aaronvargas
источник
Это работает как шарм, но я не до конца понимаю. Если первый элемент все еще виден, как имеет смысл получить следующего ребенка? Не должен ли setSelectionFromTop с новым индексом вызывать отображение списка, начиная со второго дочернего элемента? Как я все еще вижу первый?
Тафи
@tafi, первый элемент может быть «частично» видимым. Таким образом, верхняя часть этого элемента будет выше экрана и, следовательно, будет отрицательным числом. (Это вызывает другие проблемы, которые я не помню ...) Таким образом, мы используем «верх» второго элемента для размещения, который должен быть всегда (?), Иначе вначале не было бы прокрутки. !
aaronvargas
6
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);
        }
    }
}

Достаточно

Лео Фан
источник
Привет, ваш код выше поможет мне со списком RecyclerView, где состояние экземпляра не сохраняется между действиями? Я разместил следующий вопрос здесь: stackoverflow.com/questions/35413495/… ?
AJW
6

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

Райан Ньюсом
источник
1

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

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

Этот код переопределяет кнопку «вверх», чтобы вести себя так же, как кнопка «назад», поэтому в случае 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)); }

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

Mazvél
источник
1

Разве недостаточно просто android:saveEnabled="true"в объявлении ListView xml?

Андреа Ричиарди
источник
1
Android: saveEnabled по умолчанию имеет значение true. Это ничего не делает.
Брэд
нет. этого недостаточно, вы должны следовать stackoverflow.com/questions/3014089/… ИЛИ stackoverflow.com/questions/3014089/…
Рахул Мандалия
1

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

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

ВЫ ДОЛЖНЫ ЗВОНИТЬ В ПОЧТУ И В РЕЗЬБЕ!

Эндрю Снек
источник
1

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

val state = myList.layoutManager.onSaveInstanceState()

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

    myList.adapter.things = newThings
    myList.layoutManager.onRestoreInstanceState(state)
}
Майкл Петерсон
источник
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());
          }
     }
}
saulobrito
источник
0

Если вы сохраняете / восстанавливаете свою позицию прокрутки, ListViewвы по сути дублируете функциональность, уже реализованную в платформе Android. Сама по себе ListViewточная позиция прокрутки восстанавливается сама по себе, за исключением одного предупреждения: как упомянул @aaronvargas, есть ошибка AbsListView, которая не позволяет восстановить позицию точной прокрутки для первого элемента списка. Тем не менее, лучший способ восстановить положение прокрутки - не восстанавливать его. Android Framework сделает это лучше для вас. Просто убедитесь, что вы выполнили следующие условия:

  • убедитесь, что вы не вызвали 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

Для действия, производного от 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;
Perotin
источник
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);
}
Магнус W
источник
0

Мой ответ для Firebase, а позиция 0 - это обходной путь

Parcelable state;

DatabaseReference everybody = db.getReference("Everybody Room List");
    everybody.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
            state = listView.onSaveInstanceState(); // Save
            progressBar.setVisibility(View.GONE);
            arrayList.clear();
            for (DataSnapshot messageSnapshot : dataSnapshot.getChildren()) {
                Messages messagesSpacecraft = messageSnapshot.getValue(Messages.class);
                arrayList.add(messagesSpacecraft);
            }
            listView.setAdapter(convertView);
            listView.onRestoreInstanceState(state); // Restore
        }

        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {
        }
    });

и convertView

позиция 0 добавить пустой элемент, который вы не используете

public class Chat_ConvertView_List_Room extends BaseAdapter {

private ArrayList<Messages> spacecrafts;
private Context context;

@SuppressLint("CommitPrefEdits")
Chat_ConvertView_List_Room(Context context, ArrayList<Messages> spacecrafts) {
    this.context = context;
    this.spacecrafts = spacecrafts;
}

@Override
public int getCount() {
    return spacecrafts.size();
}

@Override
public Object getItem(int position) {
    return spacecrafts.get(position);
}

@Override
public long getItemId(int position) {
    return position;
}

@SuppressLint({"SetTextI18n", "SimpleDateFormat"})
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = LayoutInflater.from(context).inflate(R.layout.message_model_list_room, parent, false);
    }

    final Messages s = (Messages) this.getItem(position);

    if (position == 0) {
        convertView.getLayoutParams().height = 1; // 0 does not work
    } else {
        convertView.getLayoutParams().height = RelativeLayout.LayoutParams.WRAP_CONTENT;
    }

    return convertView;
}
}

Я видел эту работу временно, не мешая пользователю, я надеюсь, что она работает для вас

Trk
источник
0

используйте этот код ниже:

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);
Рохит Лалвани
источник
0

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

Перед созданием:

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

 }

OnStart:

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

OnStop:

@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, я также выкладываю здесь решение для этого:

Перед созданием:

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

OnStart:

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

OnStop:

@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();
    }
}
Джон Т
источник
0

Чтобы уточнить отличный ответ Райана Ньюсома и настроить его для фрагментов и для обычного случая, когда мы хотим перейти от «основного» фрагмента ListView к «подробному» фрагменту и затем вернуться к «главному»

    private View root;
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
           if(root == null){
             root = inflater.inflate(R.layout.myfragmentid,container,false);
             InitializeView(); 
           } 
           return root; 
        }

    public void InitializeView()
    {
        ListView listView = (ListView)root.findViewById(R.id.listviewid);
        BaseAdapter adapter = CreateAdapter();//Create your adapter here
        listView.setAdpater(adapter);
        //other initialization code
    }

Волшебство здесь в том, что когда мы возвращаемся от фрагмента подробностей к фрагменту ListView, представление не воссоздается, мы не устанавливаем адаптер ListView, поэтому все остается, как мы его оставили!

befstrat
источник