Pregunta

I tienen una larga ListView que el usuario puede desplazarse alrededor antes de regresar a la pantalla anterior. Cuando el usuario abre esta ListView nuevo, quiero que la lista se desplaza al mismo punto que era anteriormente. Cualquier ideas sobre cómo lograr esto?

¿Fue útil?

Solución

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() vuelve el elemento de lista superior visible. Sin embargo, este artículo puede ser desplazado parcialmente fuera de la vista, y si desea restaurar la posición de desplazamiento exacto de la lista que necesita para obtener este desplazamiento. Así ListView.getChildAt(0) devuelve el View para el elemento de la lista superior, y vuelve luego View.getTop() - mList.getPaddingTop() su desplazamiento relativo de la parte superior de la ListView. Entonces, para restaurar la posición de desplazamiento del ListView, que llamamos ListView.setSelectionFromTop() con el índice del elemento que queremos y un desplazamiento a la posición de su borde superior de la parte superior de la ListView.

Otros consejos

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

he adoptado la solución sugerida por @ (Kirk Woll), y funciona para mí. También he visto en el código fuente de Android de 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 ListActivity derivados:

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

A continuación, algunas anulaciones método:

@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 ponerla en la lista.

En mi dispositivo Froyo, esto funciona en ambos cuando se cambia la orientación del teléfono, y cuando se edita un elemento y volver a la lista.

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 se restablecerá la lista hasta el elemento suministrado. Si no está en modo de contacto del elemento de hecho se seleccionará si está en modo táctil del elemento sólo 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);

he encontrado algo interesante acerca de esto.

He intentado setSelection y scrolltoXY pero no funcionaba en absoluto, la lista se mantuvo en la misma posición, después de algún ensayo y error Tengo 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 Ejecutable intenta runOnUiThread no funciona bien (al menos en algunos dispositivos)

Este es un muy extraño solución para algo que debería ser sencillo.

ATENCIÓN !! Hay un error en AbsListView que no permite la onSaveState () para trabajar correctamente si la ListView.getFirstVisiblePosition () es 0.

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

desde AbsListView.java:1650 (comentarios mío)

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

Sin embargo, en esta situación, el 'top' en el código siguiente será un número negativo que causa otros problemas que impiden que el estado de ser 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);
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

Para algunos en busca de una solución a este problema, la raíz del problema puede estar donde está configurando el adaptador de vistas de lista. Después de configurar el adaptador en la vista de lista, se restablece la posición de desplazamiento. Pero es algo a tener en cuenta. Moví la configuración del adaptador en mi onCreateView después nos agarra la referencia a la vista de lista, y resuelto el problema para mí. =)

Am publicar esto porque estoy había mencionado a nadie sorprende esto.

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

Este código reemplazará el botón "arriba" a comportarse de la misma forma que el botón de retroceso 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 scrollPosition 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 se puede ir a otra actividad de la actividad de detalles el botón hasta que volverá de nuevo a esa actividad por lo que tendrá para manipular la historia BackButton para que esto funcione.

No es simplemente android:saveEnabled="true" en el ListView lo suficientemente declaración XML?

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 CORREOS Y EN HILO!

Se puede mantener el estado de desplazamiento después de una recarga si se guarda el estado antes de que vuelva a cargar y restaurarlos después. En mi caso hice una solicitud de red asíncrona y vuelve a cargar la lista en una devolución de llamada después de que se ha completado. Aquí es donde yo restaure el estado. Ejemplo de código es Kotlin.

val state = myList.layoutManager.onSaveInstanceState()

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

    myList.adapter.things = newThings
    myList.layoutManager.onRestoreInstanceState(state)
}

Si está utilizando fragmentos alojados en una actividad que 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());
          }
     }
}

Si va a guardar / restaurar la posición de desplazamiento del mismo ListView que son esencialmente de la duplicación de la funcionalidad ya implementada en el marco androide. La posición de desplazamiento fina restauraciones ListView simplemente bien por sí sola excepción de una advertencia: como @aaronvargas mencionan que hay un error en AbsListView que no le dejará para restablecer 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 no es para restaurarlo. marco Android hará que sea mejor para usted. Sólo asegúrese de que haya cumplido con las siguientes condiciones:

  • asegurarse de que no haya llamado método setSaveEnabled(false) y no establecer el atributo de android:saveEnabled="false" de la lista en el archivo de diseño xml
  • para el método de anulación ExpandableListView long getCombinedChildId(long groupId, long childId) para que vuelva número de larga positivo (aplicación por defecto en BaseExpandableListAdapter clase rendimientos número negativo). Estos son algunos 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 ListView o ExpandableListView se utiliza en un fragmento no recrear el fragmento sobre la actividad de recreo (después de la rotación de la pantalla por ejemplo). Obtener el fragmento con el método findFragmentByTag(String tag).
  • asegurarse de que el ListView tiene una android:id y es único.

Para evitar la salvedad antes mencionada con el primer elemento de la lista se puede crear su adaptador de la forma en que regresa maniquí especial de cero píxeles de altura para la vista ListView en la posición 0. Aquí está el ejemplo simple muestra de proyectos ListView y ExpandableListView restaurar su posición de desplazamiento finas mientras que sus posiciones de desplazamiento no se guardan de forma explícita / restaurado. preciso de la posición de desplazamiento se restaura perfectamente incluso para los complejos escenarios con conmutación temporal para alguna otra aplicación, rotación de pantalla doble y cambiar de nuevo a la aplicación de prueba. Tenga en cuenta, si usted está saliendo explícitamente la aplicación (pulsando el botón Atrás) no se guardará la posición de desplazamiento (así como todas las demás vistas no guardará su estado). https://github.com/voromto/RestoreScrollPosition/releases

Para una actividad derivan de ListActivity que implementa LoaderManager.LoaderCallbacks utilizando un SimpleCursorAdapter no funcionó para restaurar la posición en onreset (), porque la actividad fue casi siempre reinicia y el adaptador se vuelve a cargar cuando la vista detalles se cerró. 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;

Ninguna de las soluciones que se ofrecen aquí parecía funcionar para mí. En mi caso, tengo una 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é almacenar el estado de mi clase Application personalizado. El siguiente código debe 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 permite almacenar el estado fuera de la instancia fragmento. Si no te gusta la idea de tener un campo estático en su clase de aplicación, supongo que se podría hacer mediante la implementación de una interfaz de fragmentos y almacenar el estado en su actividad.

Otra solución sería la de almacenar en SharedPreferences, pero se vuelve un poco más complicado, y usted tendría que asegurarse de que se borre en el inicio de aplicaciones a menos que desee el estado que se conserva después de los lanzamientos de aplicaciones.

Además, para evitar la "posición de desplazamiento no se guarda al primer elemento es visible", se puede visualizar un primer elemento ficticio con la altura 0px. Esto puede lograrse reemplazando getView() en su adaptador, como esto:

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

Mi respuesta es para Firebase y la posición 0 es una solución

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ñadir un elemento en blanco que no se 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

utilizar 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 siempre que su actualización de sus datos utilizan este código a continuación:

adapter.notifyDataSetChanged();
mList.setSelectionFromTop(index, top);

Estoy usando FirebaseListAdapter y no pude conseguir ninguna de las soluciones para el trabajo. Terminé haciendo esto. Supongo que hay formas más elegantes, pero esto es una solución completa y de trabajo.

Antes onCreate:

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

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

Debido a que también tenía que resolver esto por FirebaseRecyclerAdapter que lo pongo aquí la solución para eso también:

Antes onCreate:

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

Dentro de la 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();
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top