Mantener / Guardar / Restaurar la posición de desplazamiento al volver a un ListView

397

Tengo mucho tiempo para ListViewque el usuario pueda desplazarse antes de volver a la pantalla anterior. Cuando el usuario abre esto ListViewnuevamente, quiero que la lista se desplace al mismo punto en el que se encontraba anteriormente. ¿Alguna idea sobre cómo lograr esto?

rantravee
fuente
22
Creo que las soluciones mencionadas por Eugene Mymrin / Giorgio Barchiesi son mejores que la respuesta aceptada
Mira Weller

Respuestas:

631

Prueba esto:

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

Explicación:

ListView.getFirstVisiblePosition()devuelve el elemento de la lista visible superior. Pero este elemento se puede desplazar parcialmente fuera de la vista, y si desea restaurar la posición exacta de desplazamiento de la lista, necesita obtener este desplazamiento. Por lo tanto, ListView.getChildAt(0)devuelve el Viewpara el elemento de la lista superior y luego View.getTop() - mList.getPaddingTop()devuelve su desplazamiento relativo desde la parte superior del ListView. Luego, para restaurar la ListViewposición de desplazamiento del 's, llamamos ListView.setSelectionFromTop()con el índice del elemento que queremos y un desplazamiento para colocar su borde superior desde la parte superior del ListView.

ian
fuente
2
Realmente no entiendo cómo funciona esto. Estoy usando listView.getFirstVisiblePosition () solo y entiendo eso, pero no estoy seguro de lo que está sucediendo aquí. ¿Alguna posibilidad de una breve explicación? :)
HXCaine
56
Entonces ListView.getFirstVisiblePosition () devuelve el elemento de la lista visible superior. Pero este elemento se puede desplazar parcialmente fuera de la vista, y si desea restaurar la posición exacta de desplazamiento de la lista, necesita obtener este desplazamiento. Entonces ListView.getChildAt (0) devuelve la Vista para el elemento de lista superior, y luego View.getTop () devuelve su desplazamiento relativo desde la parte superior de ListView. Luego, para restaurar la posición de desplazamiento de ListView, llamamos a ListView.setSelectionFromTop () con el índice del elemento que queremos y un desplazamiento para colocar su borde superior desde la parte superior de ListView. ¿Todo claro?
Ian
15
Esto se puede hacer aún más simple a utilizar una línea para ahorrar: int index = mList.getFirstVisiblePosition();y sólo una línea para restaurar: mList.setSelectionFromTop(index, 0);. Gran respuesta sin embargo (+1)! He estado buscando una solución elegante a este problema.
Phil
17
@Phil, su solución simplista no funciona para restaurar la posición exacta de desplazamiento.
Ixx
2
como dijo @nbarraille, el código debería leer más como "v.getTop () - mList.getPaddingTop ()". De lo contrario, pasarás una hora como lo hice tratando de descubrir por qué siempre restaura un cabello ...
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
fuente
106
Creo que esta es la mejor respuesta ya que este método restaura la posición exacta de desplazamiento y no solo el primer elemento visible.
Mira Weller
99
Gran respuesta, pero no funcionará al cargar contenido dinámicamente y desea guardar el estado en el método onPause ().
Gio
13
Gran solución. Solo un problema. Si los elementos son grandes y se desplaza, pero el primer elemento aún está visible, la lista salta al principio. si te desplazas al segundo elemento o en cualquier otro lugar, todo funciona
aprobada
44
@passsy ¿Alguna vez encontró una solución para que la lista saltara al principio?
Papajohn000
2
Como dijo aaronvargas, esto no podría funcionar cuando ListView.getFirstVisiblePosition () es 0.
VinceStyling
54

Adopté la solución sugerida por @ (Kirk Woll), y funciona para mí. También he visto en el código fuente de Android para la aplicación "Contactos", que utilizan una técnica similar. Me gustaría agregar más detalles: en la parte superior de mi clase derivada de ListActivity:

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

Entonces, algunos métodos anulan:

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

Por supuesto, "loadData" es mi función para recuperar datos de la base de datos y ponerlos en la lista.

En mi dispositivo Froyo, esto funciona tanto cuando cambias la orientación del teléfono como cuando editas un elemento y vuelves a la lista.

Giorgio Barchiesi
fuente
1
Esta solución funcionó para mí. Los otros no. ¿Cómo se comporta guardar / restaurar el estado de la instancia de ListView con respecto a la eliminación e inserción de elementos en ListView?
Bryan
2
Para su información, si usa esto en un fragmento (estoy usando un fragmento de lista) y navega a otros fragmentos, esto se bloqueará porque la vista de contenido no se crea cuando se llama mListState = getListView().onSaveInstanceState().Principalmente en la rotación del dispositivo.
Mgamerz
2
onRestoreInstanceStatenunca se llama :(
dVaffection
1
@GiorgioBarchiesi ¿Quién es @ (Kirk Wool) cuya solución funciona para usted? El usuario aparentemente cambió su nombre.
Piovezan
1
Sí, esa es la religión oficial. Pero en mi caso, los datos se cargan localmente, tienen una longitud muy limitada y se cargan en una fracción de segundo, así que me puse erótico :-)
Giorgio Barchiesi el
26

Una forma muy simple:

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

//Here u should save the currentPosition anywhere

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

El método setSelection restablecerá la lista al elemento suministrado. Si no está en modo táctil, el elemento se seleccionará realmente si en modo táctil solo se colocará en la pantalla.

Un enfoque más complicado:

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
fuente
2
Hubo un error en su respuesta. El método se llama setSelection
Janusz
La idea funciona bien, pero hay un pseudoproblema. La primera posición visible puede mostrarse solo un poco (tal vez solo una pequeña parte de la fila), y setSelection () configura esta fila para que sea completamente visible cuando la restauración es hecho.
rantravee
Por favor, eche un vistazo a mi segunda opción. Lo acabo de agregar a mi primera respuesta
Francesco Laurita
27
view.getScrollX () y view.getScrollY () siempre devuelven 0!
rantravee
3
Eche un vistazo a mi solución (aceptada) anterior usando View.getChildAt () y View.getTop (). No puede usar View.getScrollY () en un ListView porque un ListView mantiene su desplazamiento internamente.
Ian
17

Encontré algo interesante sobre esto.

Intenté setSelection y scrolltoXY pero no funcionó en absoluto, la lista permaneció en la misma posición, después de una prueba y error obtuve el siguiente código que funciona

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

Si en lugar de publicar el Runnable intenta ejecutar runOnUiThread, tampoco funciona (al menos en algunos dispositivos)

Esta es una solución muy extraña para algo que debería ser sencillo.

shalafi
fuente
1
¡Experimenté el mismo problema! Y setSelectionFromTop()no funciona
ofavre
+1. listnew.post (), no funciona en algunos dispositivos, y en otros dispositivos no funciona en ciertos casos, pero runOnUIThread funciona bien.
Omar Rehman
@ Omar, ¿puedes dar algunos ejemplos de dispositivos y sistemas operativos en los que esto no funciona? Probé un HTC G2 (2.3.4) y un Galaxy Nexus (4.1.2) y funcionó en ambos. Ninguna de las otras respuestas en este hilo funcionó para ninguno de mis dispositivos.
Dan J
2
listview.post funciona bien en mi Galaxy Note con 4.0.4, pero no funciona en mi Nexus One con 2.3.6. Sin embargo, onOnUIThread (acción) funciona bien en ambos dispositivos.
Omar Rehman
11

¡¡PRECAUCIÓN!! Hay un error en AbsListView que no permite que onSaveState () funcione correctamente si ListView.getFirstVisiblePosition () es 0.

Entonces, si tiene imágenes grandes que ocupan la mayor parte de la pantalla y se desplaza a la segunda imagen, pero se muestra un poco de la primera, la posición de desplazamiento no se guardará ...

de AbsListView.java:1650 (comentarios míos)

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

Pero en esta situación, la 'parte superior' en el código a continuación será un número negativo que causa otros problemas que impiden que el estado se restaure correctamente. Entonces, cuando el 'top' es negativo, obtenga el próximo hijo

// 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
fuente
Funciona de maravilla pero no lo entiendo completamente. Si el primer elemento aún está visible, ¿qué sentido tiene obtener el siguiente elemento secundario? ¿No debería establecerSelectionFromTop con el nuevo índice que la lista se muestre a partir del segundo elemento secundario? ¿Cómo sigo viendo el primero?
tafi
@tafi, el primer elemento podría ser 'parcialmente' visible. Por lo tanto, la parte superior de ese elemento estaría sobre la pantalla y, por lo tanto, sería un número negativo. (Esto causa otros problemas que no recuerdo ...) Por lo tanto, utilizamos la 'parte superior' del segundo elemento para la colocación, que siempre debería estar (?) Disponible, o no habría ningún desplazamiento en primer 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);
        }
    }
}

Eso es suficiente

Leo Phan
fuente
Hola, ¿me ayudaría su código anterior con una lista de RecyclerView donde el estado de instancia no se guarda entre actividades? Publiqué la siguiente pregunta aquí: stackoverflow.com/questions/35413495/… ?
AJW
6

Para algunos que buscan una solución a este problema, la raíz del problema puede ser dónde está configurando su adaptador de vistas de lista. Después de configurar el adaptador en la vista de lista, restablece la posición de desplazamiento. Solo algo a tener en cuenta. Moví la configuración del adaptador en mi onCreateView después de que tomamos la referencia a la vista de lista, y me resolvió el problema. =)

Ryan Newsom
fuente
1

Estoy publicando esto porque me sorprende que nadie haya mencionado esto.

Después de que el usuario haga clic en el botón Atrás, volverá a la vista de lista en el mismo estado en que salió de ella.

Este código anulará el botón "arriba" para comportarse de la misma manera que el botón Atrás, por lo que en el caso de Vista de lista -> Detalles -> Volver a vista de lista (y ninguna otra opción) este es el código más simple para mantener la posición de desplazamiento y el contenido en la vista de lista.

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

Precaución: si puede ir a otra actividad desde la actividad de detalles, el botón arriba lo regresará a esa actividad, por lo que tendrá que manipular el historial del botón de retroceso para que esto funcione.

Mazvél
fuente
1

LA MEJOR SOLUCIÓN ES:

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

DEBE LLAMAR EN POST Y EN HILO!

Andrew Sneck
fuente
1

Puede mantener el estado de desplazamiento después de una recarga si guarda el estado antes de volver a cargarlo y restaurarlo después. En mi caso, hice una solicitud de red asincrónica y volví a cargar la lista en una devolución de llamada después de que se completara. Aquí es donde restauro el estado. El ejemplo de código es Kotlin.

val state = myList.layoutManager.onSaveInstanceState()

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

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

Si está utilizando fragmentos alojados en una actividad, puede hacer algo como esto:

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

Si está guardando / restaurando la posición de desplazamiento de ListViewusted mismo, básicamente está duplicando la funcionalidad ya implementada en el marco de Android. La ListViewposición de desplazamiento fina restauraciones simplemente bien por sí sola excepción de una advertencia: como @aaronvargas mencionan que hay un error en el AbsListViewque no va a dejar que para restaurar la posición de desplazamiento bien para el primer elemento de la lista. Sin embargo, la mejor manera de restaurar la posición de desplazamiento es no restaurarla. El marco de Android lo hará mejor para ti. Solo asegúrese de cumplir con las siguientes condiciones:

  • asegúrese de no haber llamado al setSaveEnabled(false)método y no haber establecido el android:saveEnabled="false"atributo para la lista en el archivo de diseño xml
  • para el método de ExpandableListViewanulación long getCombinedChildId(long groupId, long childId)para que devuelva un número largo positivo (la implementación predeterminada en la clase BaseExpandableListAdapterdevuelve un número negativo). Aquí hay ejemplos:

.

@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;
}
  • si ListViewo ExpandableListViewse usa en un fragmento, no vuelva a crear el fragmento en la recreación de la actividad (después de la rotación de la pantalla, por ejemplo). Obtenga el fragmento con el findFragmentByTag(String tag)método.
  • asegúrese de que ListViewtiene un android:idy es único.

Para evitar la advertencia mencionada anteriormente con el primer elemento de la lista, puede diseñar su adaptador de la forma en que devuelve una vista simulada especial de altura de cero píxeles para la ListViewposición 0. Aquí está el ejemplo simple que el proyecto muestra ListViewy ExpandableListViewrestaura sus posiciones de desplazamiento fino, mientras que sus posiciones de desplazamiento no se guardan explícitamente / restaurado La posición de desplazamiento fino se restaura perfectamente incluso para los escenarios complejos con cambio temporal a alguna otra aplicación, rotación de doble pantalla y cambio a la aplicación de prueba. Tenga en cuenta que si sale explícitamente de la aplicación (presionando el botón Atrás), la posición de desplazamiento no se guardará (así como todas las demás Vistas no guardarán su estado). https://github.com/voromto/RestoreScrollPosition/releases

Sergey
fuente
0

Para una actividad derivada de ListActivity que implementa LoaderManager.LoaderCallbacks usando un SimpleCursorAdapter, no funcionó para restaurar la posición en onReset (), porque la actividad casi siempre se reiniciaba y el adaptador se recargaba cuando se cerraba la vista de detalles. El truco consistía en restaurar la posición en onLoadFinished ():

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

en onLoadFinished ():

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

en onBackPressed ():

// Show the top item at next start of the app
index = 0;
top = 0;
Perotina
fuente
0

Ninguna de las soluciones ofrecidas aquí parecía funcionar para mí. En mi caso, tengo un ListViewen Fragmentque estoy reemplazando en un FragmentTransaction, por lo que Fragmentse crea una nueva instancia cada vez que se muestra el fragmento, lo que significa que el ListViewestado no se puede almacenar como miembro del Fragment.

En cambio, terminé almacenando el estado en mi Applicationclase personalizada . El siguiente código debería darle una idea de cómo funciona esto:

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

}

La idea básica es que almacene el estado fuera de la instancia de fragmento. Si no le gusta la idea de tener un campo estático en su clase de aplicación, supongo que podría hacerlo implementando una interfaz de fragmentos y almacenando el estado en su actividad.

Otra solución sería almacenarlo SharedPreferences, pero se vuelve un poco más complicado, y deberá asegurarse de borrarlo en el inicio de la aplicación a menos que desee que el estado persista en los lanzamientos de aplicaciones.

 

Además, para evitar la "posición de desplazamiento no guardada cuando el primer elemento está visible", puede mostrar un primer elemento ficticio con 0pxaltura. Esto se puede lograr anulando getView()en su adaptador, así:

@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
fuente
0

Mi respuesta es para Firebase y la posición 0 es una solución 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) {
        }
    });

y convertView

posición 0 a agregue un elemento en blanco que no esté utilizando

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

He visto este trabajo temporalmente sin molestar al usuario, espero que funcione para usted

Trk
fuente
0

use este código a continuación:

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

y cada vez que actualice sus datos use este código a continuación:

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

Estoy usando FirebaseListAdapter y no pude lograr que ninguna de las soluciones funcione. Terminé haciendo esto. Supongo que hay formas más elegantes, pero esta es una solución completa y funcional.

Antes de onCreate:

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

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

 }

en Inicio:

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

Como también tuve que resolver esto para FirebaseRecyclerAdapter , estoy publicando la solución aquí para eso también:

Antes de onCreate:

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

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

en Inicio:

@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
fuente
0

Para aclarar la excelente respuesta de Ryan Newsom y ajustarla para fragmentos y para el caso habitual de que queramos navegar de un fragmento ListView "maestro" a un fragmento "detalles" y luego volver al "maestro"

    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
    }

La "magia" aquí es que cuando navegamos hacia atrás desde el fragmento de detalles al fragmento ListView, la vista no se recrea, no configuramos el adaptador de ListView, ¡así que todo permanece como lo dejamos!

befstrat
fuente