Manter / Salvar / Restaurar a posição de rolagem ao retornar a um ListView

397

Tenho um longo tempo para ListViewque o usuário possa rolar antes de retornar à tela anterior. Quando o usuário abrir ListViewnovamente, desejo que a lista seja rolada para o mesmo ponto em que estava anteriormente. Alguma idéia de como conseguir isso?

rantravee
fonte
22
Eu acho que as soluções mencionadas por Eugene Mymrin / Giorgio Barchiesi são melhores do que a resposta aceita
Mira Weller

Respostas:

631

Tente o seguinte:

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

Explicação:

ListView.getFirstVisiblePosition()retorna o item da lista visível no topo. Mas esse item pode ser parcialmente rolado para fora da vista e, se você quiser restaurar a posição exata da rolagem da lista, precisará obter esse deslocamento. Portanto, ListView.getChildAt(0)retorna o Viewitem da lista superior e, em seguida, View.getTop() - mList.getPaddingTop()retorna seu deslocamento relativo da parte superior da ListView. Então, para restaurar a ListViewposição de rolagem, chamamos ListView.setSelectionFromTop()com o índice do item que queremos e um deslocamento para posicionar sua borda superior a partir da parte superior do ListView.

ian
fonte
2
Não estou realmente entendendo como isso funciona. Estou usando apenas listView.getFirstVisiblePosition () e entendo isso, mas não sei o que está acontecendo aqui. Alguma chance de uma breve explicação? :)
HXCaine
56
Portanto, ListView.getFirstVisiblePosition () retorna o item da lista visível no topo. Mas esse item pode ser parcialmente rolado para fora da vista e, se você quiser restaurar a posição exata da rolagem da lista, precisará obter esse deslocamento. Portanto, ListView.getChildAt (0) retorna a exibição do item da lista superior e, em seguida, View.getTop () retorna seu deslocamento relativo da parte superior do ListView. Em seguida, para restaurar a posição de rolagem do ListView, chamamos ListView.setSelectionFromTop () com o índice do item que queremos e um deslocamento para posicionar sua borda superior a partir do topo do ListView. Tudo limpo?
ian
15
Isto pode ser feito ainda mais simples estar usando uma linha para salvar: int index = mList.getFirstVisiblePosition();e apenas uma linha para restaurar: mList.setSelectionFromTop(index, 0);. Ótima resposta (+1)! Eu tenho procurado uma solução elegante para este problema.
Phil
17
@ Sua solução simplista não funciona para restaurar a posição exata de rolagem.
Ixx
2
como o @nbarraille disse, o código deve ler mais como "v.getTop () - mList.getPaddingTop ()." Caso contrário, você vai passar uma hora como eu fiz tentando descobrir por que ele sempre restaura apenas um fio de cabelo fora ...
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);
    }
}
Eugene Mymrin
fonte
106
Acho que essa é a melhor resposta, pois esse método restaura a posição exata de rolagem e não apenas o primeiro item visível.
21411 MiraMaller #
9
Ótima resposta, mas não funcionará ao carregar dinamicamente o conteúdo e você deseja salvar o estado no método onPause ().
18712 Gio
13
ótima solução. Apenas um problema. Se os itens forem grandes e você rolar, mas o primeiro item ainda estiver visível, a lista retornará ao topo. Se você vá até o segundo item ou tudo funciona em qualquer outro lugar
passsy
4
@passsy Você já encontrou uma solução na lista voltando ao topo?
Papajohn000
2
Como aaronvargas disse, isso não poderia funcionar quando ListView.getFirstVisiblePosition () é 0.
VinceStyling
54

Adotei a solução sugerida por @ (Kirk Woll), e ela funciona para mim. Também vi no código-fonte Android do aplicativo "Contatos" que eles usam uma técnica semelhante. Gostaria de adicionar mais alguns detalhes: No topo da minha classe derivada de ListActivity:

private static final String LIST_STATE = "listState";
private Parcelable mListState = null;

Em seguida, algum método substitui:

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

É claro que "loadData" é minha função de recuperar dados do banco de dados e colocá-los na lista.

No meu dispositivo Froyo, isso funciona quando você altera a orientação do telefone e quando edita um item e volta à lista.

Giorgio Barchiesi
fonte
11
Esta solução funcionou para mim. Os outros não. Como se salva / restaura o estado da instância do ListView com relação à exclusão e inserção de itens no ListView?
21713 Bryan
2
Para sua informação, se você usar isso em um fragmento (estou usando um fragmento de lista) e navegar para outros fragmentos, isso falhará porque a exibição do conteúdo não é criada ao chamar mListState = getListView().onSaveInstanceState().Principalmente na rotação do dispositivo.
Mgamerz
2
onRestoreInstanceStatenunca é chamado :(
dVaffection
11
@GiorgioBarchiesi Quem é @ (Kirk Wool) cuja solução funciona para você? O usuário aparentemente mudou seu nome.
Piovezan
11
Sim, essa é a religião oficial. Mas no meu caso os dados são carregados comprimento localmente, tem muito limitado e cargas em uma fração de segundo, então eu fui erethic :-)
Giorgio Barchiesi
26

Uma maneira muito simples:

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

//Here u should save the currentPosition anywhere

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

O método setSelection redefinirá a lista para o item fornecido. Se não estiver no modo de toque, o item será realmente selecionado se, no modo de toque, o item será posicionado apenas na tela.

Uma abordagem mais complicada:

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);
Francesco Laurita
fonte
2
houve um erro na sua resposta. O método é chamado setSelection
Janusz
A ideia funciona bem, mas há um pseudo problema. A primeira posição visível pode ser exibida apenas um pouco (talvez apenas uma pequena parte da linha) e setSelection () define essa linha para ficar completamente visível quando a restauração é concluída. fez.
rantravee
Por favor, dê uma olhada na minha segunda opção. Acabei de adicioná-lo à minha primeira resposta #
Francesco Laurita
27
view.getScrollX () e view.getScrollY () sempre retornam 0!
rantravee
3
Dê uma olhada na minha solução (aceita) acima usando View.getChildAt () e View.getTop (). Você não pode usar View.getScrollY () em um ListView porque um ListView mantém sua rolagem interna.
ian
17

Encontrei algo interessante sobre isso.

Tentei setSelection e scrolltoXY, mas não funcionou, a lista permaneceu na mesma posição, após algumas tentativas e erros, obtive o seguinte código que funciona

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

Se, em vez de postar o Runnable, você tentar o runOnUiThread, ele também não funcionará (pelo menos em alguns dispositivos)

Essa é uma solução alternativa muito estranha para algo que deve ser direto.

shalafi
fonte
11
Eu experimentei o mesmo problema! E setSelectionFromTop()não funciona.
ofavre
+1. listnew.post (), não funciona em alguns dispositivos, e em outros dispositivos não funciona em certos casos, mas o runOnUIThread funciona bem.
Omar Rehman
@ Omar, você pode dar alguns exemplos de dispositivos e sistemas operacionais nos quais isso não funciona? Eu tentei um HTC G2 (2.3.4) e um Galaxy Nexus (4.1.2) e funcionou em ambos. Nenhuma das outras respostas neste tópico funcionou para nenhum dos meus dispositivos.
Dan J
2
O listview.post funciona bem no meu Galaxy Note com 4.0.4, mas não no meu Nexus One com o 2.3.6. No entanto, onOnUIThread (ação) funciona bem nos dois dispositivos.
Omar Rehman
11

CUIDADO!! Há um erro no AbsListView que não permite que o onSaveState () funcione corretamente se o ListView.getFirstVisiblePosition () for 0.

Portanto, se você tiver imagens grandes que ocupam a maior parte da tela e rolar para a segunda imagem, mas um pouco da primeira estiver sendo exibida, a posição de rolagem não será salva ...

de AbsListView.java:1650 (comentários meus)

// this will be false when the firstPosition IS 0
if (haveChildren && mFirstPosition > 0) {
    ...
} else {
    ss.viewTop = 0;
    ss.firstId = INVALID_POSITION;
    ss.position = 0;
}

Mas nessa situação, o 'top' no código abaixo será um número negativo que causa outros problemas que impedem a restauração correta do estado. Então, quando o 'top' for negativo, pegue o próximo filho

// 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
fonte
Funciona como um encanto, mas não o entendo completamente. Se o primeiro item ainda estiver visível, como faz sentido obter o próximo filho? Não deve setSelectionFromTop com o novo índice fazer com que a lista seja mostrada a partir do segundo filho? Como ainda estou vendo o primeiro?
Tafi
@tafi, o primeiro item pode ser 'parcialmente' visível. Portanto, a parte superior desse item estaria acima da tela e, portanto, seria um número negativo. (Isso causa outros problemas que não me lembro ...) Portanto, usamos o 'topo' do segundo item para a veiculação, que deve sempre (?) Estar disponível ou não haveria rolagem em primeiro lugar !
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);
        }
    }
}

É o bastante

Leo Phan
fonte
Olá, seu código acima me ajudaria com uma lista do RecyclerView em que instancestate não está sendo salvo entre as atividades? Publiquei a seguinte pergunta aqui: stackoverflow.com/questions/35413495/… ?
AJW
6

Para quem procura uma solução para esse problema, a raiz do problema pode estar no local em que você está configurando o adaptador de exibições de lista. Depois de definir o adaptador na vista de lista, ele redefine a posição de rolagem. Apenas algo a considerar. Mudei a configuração do adaptador para o meu onCreateView depois de pegarmos a referência ao listview e ele resolveu o problema para mim. =)

Ryan Newsom
fonte
1

Estou postando isso porque estou surpreso que ninguém tenha mencionado isso.

Depois que o usuário clicar no botão Voltar, ele retornará à exibição de lista no mesmo estado em que saiu dela.

Este código substituirá o botão "para cima" para se comportar da mesma forma que o botão Voltar. Portanto, no caso de Listview -> Detalhes -> Voltar para o Listview (e nenhuma outra opção), este é o código mais simples para manter a posição de rolagem e o conteúdo na lista.

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

Cuidado: Se você puder ir para outra atividade a partir da atividade de detalhes, o botão para cima retornará a ela para que você tenha que manipular o histórico do botão de volta para que isso funcione.

Mazvél
fonte
1

A MELHOR SOLUÇÃO É:

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

VOCÊ DEVE LIGAR NO PÓS E NA LINHA!

Andrew Sneck
fonte
1

Você pode manter o estado de rolagem após uma recarga se salvar o estado antes de recarregá-lo e restaurá-lo depois. No meu caso, fiz uma solicitação de rede assíncrona e recarreguei a lista em um retorno de chamada após a conclusão. É aqui que eu restauro o estado. A amostra do código é Kotlin.

val state = myList.layoutManager.onSaveInstanceState()

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

    myList.adapter.things = newThings
    myList.layoutManager.onRestoreInstanceState(state)
}
Michael Peterson
fonte
0

Se você estiver usando fragmentos hospedados em uma atividade, poderá fazer algo assim:

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
fonte
0

Se você está salvando / restaurando a posição de rolagem de ListViewsi mesmo, está basicamente duplicando a funcionalidade já implementada na estrutura do Android. A ListViewrestauração restaura a posição de rolagem fina por si só, exceto uma ressalva: como @aaronvargas mencionou, há um erro AbsListViewque não permite restaurar a posição de rolagem fina para o primeiro item da lista. No entanto, a melhor maneira de restaurar a posição de rolagem não é restaurá-la. A estrutura do Android fará melhor para você. Apenas verifique se você atendeu às seguintes condições:

  • verifique se você não chamou o setSaveEnabled(false)método e não configurou o android:saveEnabled="false"atributo para a lista no arquivo de layout xml
  • para ExpandableListViewsubstituir o long getCombinedChildId(long groupId, long childId)método para que ele retorne um número longo positivo (a implementação padrão na classe BaseExpandableListAdapterretorna um número negativo). Aqui estão alguns exemplos:

.

@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;
}
  • se ListViewou ExpandableListViewfor usado em um fragmento, não o recrie na recreação da atividade (após rotação da tela, por exemplo). Obtenha o fragmento com o findFragmentByTag(String tag)método
  • verifique se o ListViewpossui android:ide é exclusivo.

Para evitar a advertência acima mencionada com o primeiro item da lista, você pode criar seu adaptador da maneira que ele retorna uma exibição especial de altura zero de pixels dummy para a ListViewposição 0. Aqui está um exemplo de projeto simples que mostra ListViewe ExpandableListViewrestaura suas posições de rolagem fina, enquanto suas posições de rolagem não são salvas explicitamente /restaurado. A posição de rolagem fina é restaurada perfeitamente, mesmo para os cenários complexos, com mudança temporária para outro aplicativo, rotação dupla da tela e retorno ao aplicativo de teste. Observe que, se você sair explicitamente do aplicativo (pressionando o botão Voltar), a posição de rolagem não será salva (assim como todas as outras exibições não salvarão seu estado). https://github.com/voromto/RestoreScrollPosition/releases

Sergey
fonte
0

Para uma atividade derivada de ListActivity que implementa LoaderManager.LoaderCallbacks usando um SimpleCursorAdapter, não funcionou para restaurar a posição em onReset (), porque a atividade era quase sempre reiniciada e o adaptador era recarregado quando a exibição de detalhes era fechada. O truque era restaurar a posição em onLoadFinished ():

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

em onLoadFinished ():

// restore the selected item which was saved on item click
// when details are closed and list is shown again
getListView().setSelectionFromTop(index, top);

em onBackPressed ():

// Show the top item at next start of the app
index = 0;
top = 0;
Perotin
fonte
0

Nenhuma das soluções oferecidas aqui parecia funcionar para mim. No meu caso, eu tenho um ListViewem um Fragmentque estou substituindo em um FragmentTransaction, portanto, uma nova Fragmentinstância é criada cada vez que o fragmento é mostrado, o que significa que o ListViewestado não pode ser armazenado como um membro doFragment .

Em vez disso, acabei armazenando o estado no meu costume Application classe . O código abaixo deve lhe dar uma idéia de como isso funciona:

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

}

A ideia básica é que você armazene o estado fora da instância do fragmento. Se você não gosta da ideia de ter um campo estático em sua classe de aplicativo, acho que você poderia fazê-lo implementando uma interface de fragmento e armazenando o estado em sua atividade.

Outra solução seria armazená-lo SharedPreferences, mas fica um pouco mais complicado, e você deve limpá-lo no início do aplicativo, a menos que queira que o estado persista durante o lançamento de aplicativos.

 

Além disso, para evitar a "posição de rolagem não salva quando o primeiro item estiver visível", você pode exibir um primeiro item fictício com 0pxaltura. Isso pode ser conseguido substituindo getView()seu adaptador, desta forma:

@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);
}
Magnus W
fonte
0

Minha resposta é para Firebase e a posição 0 é uma solução alternativa

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

e convertView

posição 0 a adicione um item em branco que você não está usando

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

Vi esse trabalho temporariamente sem incomodar o usuário, espero que funcione para você

Trk
fonte
0

use este código abaixo:

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

e sempre que você atualizar seus dados, use este código abaixo:

adapter.notifyDataSetChanged();
mList.setSelectionFromTop(index, top);
Rohit Lalwani
fonte
0

Estou usando o FirebaseListAdapter e não consegui fazer nenhuma das soluções funcionar. Acabei fazendo isso. Acho que existem maneiras mais elegantes, mas essa é uma solução completa e funcional.

Antes do onCreate:

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

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

Desde que eu também tive que resolver isso para FirebaseRecyclerAdapter também estou postando a solução aqui:

Antes do onCreate:

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

Dentro do 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();
    }
}
John T
fonte
0

Para esclarecer a excelente resposta de Ryan Newsom e ajustá-la para fragmentos e para o caso usual em que queremos navegar de um fragmento ListView "mestre" para um fragmento "detalhes" e depois voltar para o "mestre"

    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
    }

A "mágica" aqui é que, quando retornamos do fragmento de detalhes para o fragmento de ListView, a visualização não é recriada, não configuramos o adaptador do ListView, para que tudo permaneça como o deixamos!

befstrat
fonte