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?
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
.
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);
}
}
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.
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);
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.
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);
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
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í. =)
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.
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?
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!
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)
}
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());
}
}
}
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 atributoandroid:saveEnabled="false"
para la lista en el archivo de diseño xml - for
ExpandableListView
overridelong getCombinedChildId(long groupId, long childId)
method so that it returns positive long number (default implementation in classBaseExpandableListAdapter
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
oExpandableListView
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étodofindFragmentByTag(String tag)
. - asegúrese de que el
ListView
tiene unandroid: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
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;
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);
}
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
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