Actualizar datos en RecyclerView y mantener su posición de desplazamiento

96

¿Cómo se actualizan los datos que se muestran en RecyclerView(llamando notifyDataSetChangeda su adaptador) y se asegura de que la posición de desplazamiento se restablezca exactamente donde estaba?

En el caso de que ListViewtodo sea bueno, todo lo que se necesita es recuperar getChildAt(0), verificar getTop()y llamar setSelectionFromTopcon los mismos datos exactos después.

No parece posible en el caso de RecyclerView.

Supongo que se supone que debo usar lo LayoutManagerque de hecho proporciona scrollToPositionWithOffset(int position, int offset), pero ¿cuál es la forma correcta de recuperar la posición y el desplazamiento?

layoutManager.findFirstVisibleItemPosition()y layoutManager.getChildAt(0).getTop()?

¿O hay una forma más elegante de hacer el trabajo?

Konrad Morawski
fuente
Se trata de los valores de ancho y alto de su RecyclerView o LayoutManager. Compruebe esta solución: stackoverflow.com/questions/28993640/…
serefakyuz
@Konard, en mi caso, tengo una lista de archivos de audio con la implementación de Seekbar y que se ejecuta con el siguiente problema: 1) Para algunos de los últimos elementos indexados, tan pronto se activó el notificarItemChanged (pos), presionó la vista en la parte inferior automáticamente. 2) Mientras se mantiene en notifyItemChanged (pos), se atasca la lista y no permite desplazarse fácilmente. Aunque haré una pregunta, pero avíseme si tiene un enfoque mejor para solucionar este problema.
Código

Respuestas:

139

Yo uso este. ^ _ ^

// Save state
private Parcelable recyclerViewState;
recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();

// Restore state
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);

Es más simple, ¡espero que te ayude!

DawnYu
fuente
1
En realidad, esta es la respuesta correcta en mi opinión. Se vuelve complicado solo por la carga de datos asincrónica, pero puede posponer la restauración.
EpicPandaForce
perfecto +2 exactamente lo que estaba buscando
Adeel Turk
@BubbleTree paste.ofcode.org/EEdjSLQbLrcQyxXpBpEQ4m Estoy tratando de implementar esto pero no funciona, ¿qué estoy haciendo mal?
Kaustubh Bhagwat
@KaustubhBhagwat Tal vez deberías marcar "recyclingViewState! = Null" antes de usarlo.
DawnYu
4
¿Dónde debo usar este código exactamente? en el método onResume?
Lucky_girl
47

Tengo un problema bastante similar. Y se me ocurrió la siguiente solución.

Usar notifyDataSetChangedes una mala idea. Debería ser más específico, luego RecyclerViewguardará el estado de desplazamiento para usted.

Por ejemplo, si solo necesita actualizar, o en otras palabras, desea que cada vista se vuelva a enlazar, simplemente haga esto:

adapter.notifyItemRangeChanged(0, adapter.getItemCount());
Kaerdan
fuente
2
Probé adapter.notifyItemRangeChanged (0, adapter.getItemCount ());, que también funciona como .notifyDataSetChanged ()
Mani
4
Esto ayudó en mi caso. Estaba insertando varios elementos en la posición 0 y con notifyDataSetChangedla posición de desplazamiento cambiaría mostrando los nuevos elementos. Sin embargo, si utilizar notifyItemRangeInserted(0, newItems.size() - 1)la RecyclerViewmantiene el estado de desplazamiento.
Carlosph
muy, muy buena respuesta, busco una solución de esa respuesta todo un día. muchas gracias ..
Gundu Bandgar
adapter.notifyItemRangeChanged (0, adapter.getItemCount ()); debe evitarse, como se indica en la última versión de Google I / O (vea la presentación sobre las entradas y salidas de la vista del reciclador), sería mejor (si tiene el conocimiento) decirle a la vista del reciclador exactamente qué elementos cambiaron en lugar de una actualización general y plana de todas las vistas.
A. Steenbergen
3
no funciona, la vista del reciclaje se actualiza a la cima a pesar de que agregué
Shaahul
21

EDITAR: Para restaurar exactamente la misma posición aparente , como en, hacer que se vea exactamente como lo hizo, necesitamos hacer algo un poco diferente (vea a continuación cómo restaurar el valor de scrollY exacto):

Guarde la posición y el desplazamiento de esta manera:

LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
int firstItem = manager.findFirstVisibleItemPosition();
View firstItemView = manager.findViewByPosition(firstItem);
float topOffset = firstItemView.getTop();

outState.putInt(ARGS_SCROLL_POS, firstItem);
outState.putFloat(ARGS_SCROLL_OFFSET, topOffset);

Y luego restaure el pergamino así:

LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
manager.scrollToPositionWithOffset(mStatePos, (int) mStateOffset);

Esto restaura la lista a su posición aparente exacta . Aparente porque se verá igual para el usuario, pero no tendrá el mismo valor de scrollY (debido a posibles diferencias en las dimensiones del diseño horizontal / vertical).

Tenga en cuenta que esto solo funciona con LinearLayoutManager.

--- Debajo de cómo restaurar el scrollY exacto, lo que probablemente hará que la lista se vea diferente ---

  1. Aplicar un OnScrollListener así:

    private int mScrollY;
    private RecyclerView.OnScrollListener mTotalScrollListener = new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            mScrollY += dy;
        }
    };

Esto almacenará la posición de desplazamiento exacta en todo momento en mScrollY.

  1. Almacene esta variable en su Bundle y restáurela en restauración de estado a una variable diferente , la llamaremos mStateScrollY.

  2. Después de la restauración del estado y después de que su RecyclerView haya restablecido todos sus datos, restablezca el desplazamiento con esto:

    mRecyclerView.scrollBy(0, mStateScrollY);

Eso es.

Tenga cuidado, que restaure el desplazamiento a una variable diferente, esto es importante, porque el OnScrollListener será llamado con .scrollBy () y posteriormente establecerá mScrollY al valor almacenado en mStateScrollY. Si no hace esto, mScrollY tendrá el doble del valor de desplazamiento (porque OnScrollListener funciona con deltas, no con desplazamientos absolutos).

El ahorro estatal en actividades se puede lograr así:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt(ARGS_SCROLL_Y, mScrollY);
}

Y para restaurar, llame a esto en su onCreate ():

if(savedState != null){
    mStateScrollY = savedState.getInt(ARGS_SCROLL_Y, 0);
}

El guardado de estado en fragmentos funciona de manera similar, pero el guardado de estado real necesita un poco de trabajo adicional, pero hay muchos artículos que tratan sobre eso, por lo que no debería tener problemas para descubrir cómo, los principios para guardar el scrollY y restaurarlo sigue siendo el mismo.

A. Steenbergen
fuente
No entiendo, ¿puedes publicar un ejemplo completo?
En realidad, esto es lo más cercano a un ejemplo completo que puede obtener si no quiere que publique toda mi actividad
A. Steenbergen
No consigo nada. Si hubiera elementos agregados al principio de la lista, ¿no sería incorrecto permanecer en la misma posición de desplazamiento, ya que ahora estará buscando diferentes elementos que se apoderaron de esta área?
desarrollador de Android
Sí, en ese caso tienes que ajustar la posición al restaurar, sin embargo, esto no es parte de la pregunta, ya que normalmente mientras giras no agregas ni quitas elementos. Eso sería un comportamiento extraño y probablemente no sea del interés del usuario, excepto en algunos casos especiales que podrían existir, que no puedo imaginar, honestamente.
A. Steenbergen
9

Sí, puede resolver este problema haciendo que el constructor del adaptador solo una vez, estoy explicando la parte de codificación aquí:

if (appointmentListAdapter == null) {
        appointmentListAdapter = new AppointmentListAdapter(AppointmentsActivity.this);
        appointmentListAdapter.addAppointmentListData(appointmentList);
        appointmentListAdapter.setOnStatusChangeListener(onStatusChangeListener);
        appointmentRecyclerView.setAdapter(appointmentListAdapter);

    } else {
        appointmentListAdapter.addAppointmentListData(appointmentList);
        appointmentListAdapter.notifyDataSetChanged();
    }

Ahora puede ver que he comprobado que el adaptador es nulo o no y solo inicializo cuando es nulo.

Si el adaptador no es nulo, entonces estoy seguro de que he inicializado mi adaptador al menos una vez.

Así que simplemente agregaré una lista al adaptador y llamaré a notifydatasetchanged.

RecyclerView siempre mantiene la última posición desplazada, por lo tanto, no tiene que almacenar la última posición, simplemente llame a notifydatasetchanged, la vista de reciclador siempre actualiza los datos sin ir al principio.

Gracias Happy Coding

Amit Singh Tomar
fuente
Creo que esto va a la respuesta aceptada @Konrad Morawski
Jithin Jude
5

Mantenga la posición de desplazamiento usando la respuesta @DawnYu para ajustar notifyDataSetChanged()así:

val recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState() 
adapter.notifyDataSetChanged() 
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)
Morten Holmgaard
fuente
perfecto ... la mejor respuesta
jlandyr
He estado buscando esta respuesta durante mucho tiempo. muchas gracias.
Alaa AbuZarifa
1

La respuesta principal de @DawnYu funciona, pero la vista de reciclaje primero se desplazará hacia la parte superior y luego volverá a la posición de desplazamiento deseada, lo que provocará una reacción de "parpadeo" que no es agradable.

Para actualizar la vista de reciclaje, especialmente después de venir de otra actividad, sin parpadear y manteniendo la posición de desplazamiento, debe hacer lo siguiente.

  1. Asegúrese de actualizar su vista de reciclador usando DiffUtil. Lea más sobre eso aquí: https://www.journaldev.com/20873/android-recyclerview-diffutil
  2. Al reanudar su actividad, o cuando desee actualizar su actividad, cargue los datos en su vista de reciclador. Usando diffUtil, solo se realizarán las actualizaciones en la vista del reciclador mientras se mantiene su posición.

Espero que esto ayude.

Moisés Mwongela
fuente
0

No he usado Recyclerview pero lo hice en ListView. Código de muestra en Recyclerview:

setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        rowPos = mLayoutManager.findFirstVisibleItemPosition();

Es el oyente cuando el usuario se desplaza. La sobrecarga de rendimiento no es significativa. Y la primera posición visible es precisa de esta manera.

El Android original
fuente
Bien, findFirstVisibleItemPositiondevuelve "la posición del adaptador de la primera vista visible", pero ¿qué pasa con el desplazamiento?
Konrad Morawski
1
¿Existe un método similar para que Recyclerview establezca el método de selección de ListView para la posición visible? Esto es lo que hice para ListView.
El androide original
1
¿Qué tal usar el método setScrollY con el parámetro dy, un valor que guardará? Si funciona, actualizaré mi respuesta. No he usado Recyclerview pero quiero hacerlo en mi futura aplicación.
El androide original
Vea mi respuesta a continuación sobre cómo guardar la posición de desplazamiento.
A. Steenbergen
1
Muy bien, lo tendré en cuenta la próxima vez, no te voté con mala intención. Sin embargo, no estoy de acuerdo con las respuestas breves e incompletas porque cuando comencé, las respuestas cortas en las que otros desarrolladores asumían que había mucha información de fondo no me ayudaron realmente, porque a menudo eran demasiado incompletas y tuve que hacer muchas preguntas aclaratorias, algo que usted generalmente no se puede hacer en publicaciones antiguas de stackoverflow. Observe también que Konrad no aceptó ninguna respuesta, lo que indica que estas respuestas no resolvieron su problema. Aunque veo su punto general.
A. Steenbergen
-1
 mMessageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
        @Override
        public void onChanged() {
            mLayoutManager.smoothScrollToPosition(mMessageRecycler, null, mMessageAdapter.getItemCount());
        }
    });

La solución aquí es seguir desplazándose por la vista de reciclaje cuando llega un mensaje nuevo.

El método onChanged () detecta la acción realizada en la vista de reciclaje.

Abhishek Chudekar
fuente
-1

Eso me está funcionando en Kotlin.

  1. Crea el Adaptador y entrega tus datos en el constructor
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>()  { ... }
  1. cambie esta propiedad y llame a notifyDataSetChanged ()
adapter.currentPole = pole
adapter.notifyDataSetChanged()

El desplazamiento de desplazamiento no cambia.

Chris8447
fuente
-2

Tuve este problema con una lista de elementos que tenían un tiempo en minutos hasta que estaban "vencidos" y necesitaban actualizarse. Actualizaría los datos y luego llamaría

orderAdapter.notifyDataSetChanged();

y se desplazaría hasta la parte superior cada vez. Lo reemplacé con

 for(int i = 0; i < orderArrayList.size(); i++){
                orderAdapter.notifyItemChanged(i);
            }

y estuvo bien. Ninguno de los otros métodos en este hilo funcionó para mí. Sin embargo, al usar este método, hizo que cada elemento individual parpadeara cuando se actualizó, por lo que también tuve que poner esto en el fragmento principal onCreateView

RecyclerView.ItemAnimator animator = orderRecycler.getItemAnimator();
    if (animator instanceof SimpleItemAnimator) {
        ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
    }
Mate
fuente
-2

Si tiene uno o más EditTexts dentro de los elementos de una vista de reciclaje, desactive el enfoque automático de estos, colocando esta configuración en el elemento primario vista vista de reciclaje:

android:focusable="true"
android:focusableInTouchMode="true"

Tuve este problema cuando comencé otra actividad lanzada desde un elemento de la vista de reciclaje, cuando regresé y configuré una actualización de un campo en un elemento con notificarItemChanged (posición) el desplazamiento de RV se mueve, y mi conclusión fue que, el enfoque automático de EditText Artículos, el código anterior resolvió mi problema.

mejor.

Akhha8
fuente
-3

Regrese si la posición anterior y la posición es la misma;

private int oldPosition = -1;

public void notifyItemSetChanged(int position, boolean hasDownloaded) {
    if (oldPosition == position) {
        return;
    }
    oldPosition = position;
    RLog.d(TAG, " notifyItemSetChanged :: " + position);
    DBMessageModel m = mMessages.get(position);
    m.setVideoHasDownloaded(hasDownloaded);
    notifyItemChanged(position, m);
}
raditya gumay
fuente