Mantener / Guardar / Restaurar la posición de desplazamiento al regresar a una vista de lista


Tengo un largo ListView que el usuario puede recorrer antes de regresar a la pantalla anterior. Cuando el usuario vuelva a abrir este ListView, quiero que la lista se desplace hasta el mismo punto que antes. ¿Alguna idea sobre cómo lograrlo?

Author: Jonik, 2010-06-10

17 answers

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 superior de la lista visible. Pero este elemento puede estar parcialmente desplazado fuera de la vista, y si desea restaurar la posición exacta de desplazamiento de la lista, debe obtener este desplazamiento. Así que ListView.getChildAt(0) devuelve el View para 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 posición de desplazamiento de ListView, llamamos a ListView.setSelectionFromTop() con el índice del elemento que queremos y un desplazamiento para posicionar su parte superior borde de la parte superior de la ListView.

 618
Author: ian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-05 09:23:12
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);
    }
}
 517
Author: Eugene Mymrin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-19 10:37:21

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 añadir algunos detalles más: 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 cambia el teléfono orientación, y cuando edite un elemento y vuelva a la lista.

 51
Author: Giorgio Barchiesi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-04-17 18:44:09

Una forma muy sencilla:

/** 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 realmente se seleccionará si está en modo táctil, el elemento 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);
 27
Author: Francesco Laurita,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-25 20:03:55

Encontré algo interesante sobre esto.

Probé setSelection y scrolltoXY pero no funcionó en absoluto, la lista permaneció en la misma posición, después de algún ensayo 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 runOnUiThread tampoco funciona (al menos en algunos dispositivos)

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

 17
Author: shalafi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-25 19:49:08

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

Así que 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á...

Desde 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, el 'top' en el siguiente código será un número negativo lo que causa otros problemas que impiden que el estado sea restaurado correctamente. Así que cuando el' top ' es negativo, obtener el siguiente 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);
 10
Author: aaronvargas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-25 20:03:15
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

 4
Author: Leo Phan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-24 02:15:00

Para algunos que buscan una solución a este problema, la raíz del problema puede ser donde 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. Sólo algo a considerar. Moví la configuración del adaptador a mi onCreateView después de tomar la referencia a la listview, y resolvió el problema para mí. =)

 3
Author: Ryan Newsom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-25 20:25:18

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 sobrescribirá el botón "arriba" para comportarse de la misma manera que el botón atrás, por lo que en el caso de Listview -> Detalles -> Volver a Listview (y no hay otras opciones) este es el código más simple para mantener la posición de desplazamiento y el contenido en listview.

 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 de la actividad detalles el botón arriba te devolverá a esa actividad por lo que tendrás que manipular el historial de botones de retroceso para que esto funcione.

 1
Author: Mazvél,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-05 11:18:21

¿No es suficiente simplemente android:saveEnabled="true" en la declaración xml de ListView?

 1
Author: Andrea Richiardi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-07 23:48:07

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

USTED DEBE LLAMAR EN POST Y EN HILO!

 1
Author: Andrew Sneck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-13 17:55:30

Puede mantener el estado de desplazamiento después de una recarga si guarda el estado antes de recargar y lo restaura después. En mi caso, hice una solicitud de red asincrónica y recargé la lista en una devolución de llamada después de completarla. 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)
}
 1
Author: Michael Peterson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-17 19:44:14

Si estás usando fragmentos alojados en una actividad puedes 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());
          }
     }
}
 0
Author: saulobrito,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-24 16:21:10

Si está guardando/restaurando la posición de desplazamiento de ListView usted está esencialmente duplicando la funcionalidad ya implementada en el marco de Android. El ListView restaura la posición de desplazamiento fino por sí solo, excepto una advertencia: como @aaronvargas mencionó, hay un error en AbsListView que no permite restaurar la posición de desplazamiento fino para el primer elemento de la lista. Sin embargo, la mejor manera de restaurar la posición de desplazamiento no es restaurarla. Android framework lo hará mejor para usted. Sólo asegúrate de usted ha cumplido con las siguientes condiciones:

  • asegúrese de que no ha llamado al método setSaveEnabled(false) y no ha establecido el atributo android:saveEnabled="false" para la lista en el archivo de diseño xml
  • for ExpandableListView override long getCombinedChildId(long groupId, long childId) method so that it returns positive long number (default implementation in class BaseExpandableListAdapter returns negative number). Estos son 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 se usa ListView o ExpandableListView 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 método findFragmentByTag(String tag).
  • asegúrese de que el ListView tiene un android:id y es único.

Para evitar la advertencia mencionada anteriormente con el primer elemento de la lista, puede crear su adaptador de la manera en que devuelve una vista de altura de cero píxeles ficticia especial para el ListView en la posición 0. Aquí está el proyecto de ejemplo simple muestra ListView y ExpandableListView restaurar sus posiciones de desplazamiento fino, mientras que sus posiciones de desplazamiento no se guardan/restauran explícitamente. Se restaura la posición de desplazamiento fino perfectamente incluso para los escenarios complejos con el cambio temporal a alguna otra aplicación, doble rotación de pantalla y el cambio de nuevo a la aplicación de prueba. Tenga en cuenta que si está saliendo 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

 0
Author: Sergey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-07 13:19:24

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 era restaurar la posición en onLoadFinished ():

En un solo clic():

// 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;
 0
Author: Perotin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-17 12:53:57

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

En su lugar, terminé almacenando el estado en mi clase personalizada Application. El siguiente código debería darte 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 usted almacena el estado fuera de la instancia de fragmento. Si no te gusta la idea de tener un campo estático en tu clase de aplicación, supongo que podrías hacerlo implementando una interfaz de fragmento y almacenando el estado en tu actividad.

Otra solución sería almacenarlo en SharedPreferences, pero se vuelve un poco más complicado, y debería asegurarse de borrarlo al iniciar la aplicación a menos que desee que el estado se mantenga durante los inicios de la aplicación.

 

También, para evitar la "posición de desplazamiento no guardada cuando el primer elemento es visible", puede mostrar un primer elemento ficticio con 0px altura. 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);
}
 0
Author: BadCash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-05 13:19:12

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 añada 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

 0
Author: Trk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-21 13:59:21