Aggiornamento dei dati in RecyclerView e mantenimento della posizione di scorrimento


96

Come si aggiornano i dati visualizzati in RecyclerView(chiamando notifyDataSetChangedil proprio adattatore) e si assicuri che la posizione di scorrimento sia ripristinata esattamente dove si trovava?

In caso di buon vecchio ListViewtutto ciò che serve è recuperare getChildAt(0), controllarlo getTop()e chiamare setSelectionFromTopcon gli stessi esatti dati in seguito.

Non sembra essere possibile in caso di RecyclerView.

Immagino che dovrei usare quello LayoutManagerche effettivamente fornisce scrollToPositionWithOffset(int position, int offset), ma qual è il modo corretto per recuperare la posizione e l'offset?

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

O c'è un modo più elegante per portare a termine il lavoro?


Riguarda i valori di larghezza e altezza di RecyclerView o LayoutManager. Controllare tale soluzione: stackoverflow.com/questions/28993640/...
serefakyuz

@Konard, Nel mio caso ho un elenco di file audio con implementazione Seekbar e in esecuzione con il seguente problema: 1) Per alcuni ultimi elementi indicizzati non appena ha attivato notifyItemChanged (pos) spinge la visualizzazione in basso automaticamente. 2) Mentre si tiene su notifyItemChanged (pos) si blocca l'elenco e non consente di scorrere facilmente. Anche se ti chiederò come domanda, fammi sapere per favore se hai un approccio migliore per risolvere questo problema.
CoDe

Risposte:


139

Io uso questo. ^ _ ^

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

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

È più semplice, spero che ti possa aiutare!


1
Questa è in realtà la risposta giusta imo. Diventa complicato solo a causa del caricamento asincrono dei dati, ma puoi posticipare il ripristino.
EpicPandaForce

perfetto +2 esattamente quello che stavo cercando
Adeel Turk

@BubbleTree paste.ofcode.org/EEdjSLQbLrcQyxXpBpEQ4m sto cercando di implementarlo ma non funziona, cosa sto facendo di sbagliato?
Kaustubh Bhagwat

@KaustubhBhagwat Forse dovresti controllare "recyclerViewState! = Null" prima di usarlo.
DawnYu

4
Dove dovrei usare esattamente questo codice? nel metodo onResume?
Lucky_girl

47

Ho un problema abbastanza simile. E ho trovato la seguente soluzione.

Usare notifyDataSetChangedè una cattiva idea. Dovresti essere più specifico, quindi RecyclerViewsalverai lo stato di scorrimento per te.

Ad esempio, se devi solo aggiornare o, in altre parole, vuoi che ogni vista venga riassociata, fallo semplicemente:

adapter.notifyItemRangeChanged(0, adapter.getItemCount());

2
Ho provato adapter.notifyItemRangeChanged (0, adapter.getItemCount ()); funziona anche come .notifyDataSetChanged ()
Mani

4
Questo ha aiutato nel mio caso. Stavo inserendo diversi elementi nella posizione 0 e con notifyDataSetChangedla posizione di scorrimento cambierei mostrando i nuovi elementi. Tuttavia, se utilizzare notifyItemRangeInserted(0, newItems.size() - 1)lo RecyclerViewmantiene lo stato di scorrimento.
Carlosph

risposta molto molto buona, sto cercando la soluzione di quella risposta intera un giorno. grazie mille ..
Gundu Bandgar

adapter.notifyItemRangeChanged (0, adapter.getItemCount ()); dovrebbe essere evitato, come indicato nell'ultimo I / O di Google (guarda la presentazione su recyclerview in and out), sarebbe meglio (se ne hai la conoscenza) dire esattamente a recyclerview quali elementi sono cambiati invece di un aggiornamento piatto generale di tutte le visualizzazioni.
A. Steenbergen

3
non funziona, la
revisione del

21

EDIT: Per ripristinare la stessa identica posizione apparente , come in, farlo sembrare esattamente come ha fatto, dobbiamo fare qualcosa di leggermente diverso (vedi sotto come ripristinare l'esatto valore scrollY):

Salva la posizione e l'offset in questo modo:

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

E poi ripristina lo scorrimento in questo modo:

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

Ciò ripristina l'elenco nella sua esatta posizione apparente . Apparente perché avrà lo stesso aspetto per l'utente, ma non avrà lo stesso valore di scorrimento (a causa di possibili differenze nelle dimensioni del layout orizzontale / verticale).

Nota che questo funziona solo con LinearLayoutManager.

--- Di seguito come ripristinare lo scorrimento esatto Y, che probabilmente renderà l'elenco diverso ---

  1. Applica un OnScrollListener in questo modo:

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

Ciò memorizzerà la posizione di scorrimento esatta in ogni momento in mScrollY.

  1. Memorizza questa variabile nel tuo bundle e ripristinala in stato di ripristino su una variabile diversa , la chiameremo mStateScrollY.

  2. Dopo il ripristino dello stato e dopo che RecyclerView ha ripristinato tutti i suoi dati, reimpostare lo scorrimento con questo:

    mRecyclerView.scrollBy(0, mStateScrollY);

Questo è tutto.

Attenzione, che ripristini lo scroll su una variabile diversa, questo è importante, perché OnScrollListener verrà chiamato con .scrollBy () e successivamente imposterà mScrollY al valore memorizzato in mStateScrollY. Se non si esegue questa operazione, mScrollY avrà il doppio del valore di scorrimento (perché OnScrollListener funziona con i delta, non con gli scroll assoluti).

Il risparmio di stato nelle attività può essere ottenuto in questo modo:

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

E per ripristinare chiamalo nel tuo onCreate ():

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

Il salvataggio dello stato in frammenti funziona in modo simile, ma il salvataggio dello stato effettivo richiede un po 'di lavoro extra, ma ci sono molti articoli che si occupano di questo, quindi non dovresti avere problemi a scoprire come, i principi del salvataggio dello scrollY e ripristinarlo rimane lo stesso.


non capisco puoi pubblicare un esempio completo?

Questo è in realtà il più vicino a un esempio completo che puoi ottenere se non vuoi che pubblichi tutta la mia attività
A. Steenbergen,

Non ottengo niente. Se sono stati aggiunti elementi all'inizio dell'elenco, non sarebbe sbagliato rimanere nella stessa posizione di scorrimento, poiché ora starai effettivamente cercando elementi diversi che hanno occupato quest'area?
sviluppatore Android

Sì, in quel caso devi regolare la posizione durante il ripristino, tuttavia, questo non fa parte della domanda, poiché di solito durante la rotazione non aggiungi o rimuovi elementi. Sarebbe strano e di comportamento e probabilmente non nell'interesse dell'utente tranne che per alcuni casi speciali che potrebbero esistere, che non riesco a immaginare, onestamente.
A. Steenbergen

9

Sì, puoi risolvere questo problema rendendo il costruttore dell'adattatore solo una volta, sto spiegando la parte di codifica qui:

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

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

Ora puoi vedere che ho verificato che l'adattatore è nullo o meno e inizializza solo quando è nullo.

Se l'adattatore non è nullo, ho la certezza di aver inizializzato il mio adattatore almeno una volta.

Quindi aggiungerò semplicemente l'elenco all'adattatore e chiamerò notifydatasetchanged.

RecyclerView mantiene sempre l'ultima posizione in scorrimento, quindi non è necessario memorizzare l'ultima posizione, basta chiamare notifydatasetchanged, la visualizzazione riciclatore aggiorna sempre i dati senza andare all'inizio.

Grazie Happy Coding


Penso che questo vada alla risposta accettata @Konrad Morawski
Jithin Jude

5

Mantieni la posizione di scorrimento usando la risposta @DawnYu per andare a capo in notifyDataSetChanged()questo modo:

val recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState() 
adapter.notifyDataSetChanged() 
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)

perfetto ... la migliore risposta
jlandyr

Ho cercato questa risposta per molto tempo. Grazie mille.
Alaa AbuZarifa,

1

La risposta più alta di @DawnYu funziona, ma la revisione del riciclo scorrerà prima verso l'alto, quindi tornerà alla posizione di scorrimento prevista causando una reazione "simile a uno sfarfallio" che non è piacevole.

Per aggiornare recyclerView, soprattutto dopo essere uscito da un'altra attività, senza sfarfallio e mantenendo la posizione di scorrimento, è necessario eseguire le seguenti operazioni.

  1. Assicurati di aggiornare la visualizzazione del riciclatore utilizzando DiffUtil. Maggiori informazioni a riguardo qui: https://www.journaldev.com/20873/android-recyclerview-diffutil
  2. Quando riprendi la tua attività, o nel punto in cui desideri aggiornare la tua attività, carica i dati nel tuo recyclerview. Utilizzando il diffUtil, verranno effettuati solo gli aggiornamenti sul recyclerview mantenendolo in posizione.

Spero che questo ti aiuti.


0

Non ho usato Recyclerview ma l'ho fatto su ListView. Codice di esempio in Recyclerview:

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

È l'ascoltatore quando l'utente sta scorrendo. Il sovraccarico delle prestazioni non è significativo. E la prima posizione visibile è precisa in questo modo.


Va bene, findFirstVisibleItemPositionrestituisce "la posizione dell'adattatore della prima vista visibile", ma per quanto riguarda l'offset.
Konrad Morawski

1
Esiste un metodo simile per Recyclerview al metodo setSelection di ListView per la posizione visibile? Questo è quello che ho fatto per ListView.
L'originale Android

1
Che ne dici di usare il metodo setScrollY con il parametro dy, un valore che salverai? Se funziona, aggiornerò la mia risposta. Non ho utilizzato Recyclerview ma lo desidero nella mia futura app.
L'originale Android

Si prega di vedere la mia risposta di seguito su come salvare la posizione di scorrimento.
A. Steenbergen

1
Va bene, lo terrò a mente la prossima volta, non ti ho votato male con malinteso. Tuttavia non sono d'accordo con risposte brevi e incomplete perché quando ho iniziato le risposte brevi in ​​cui altri sviluppatori presumevano molte informazioni di base non mi aiutavano davvero, perché spesso erano troppo incomplete e dovevo fare molte domande chiarificatrici, qualcosa che tu di solito non può fare su vecchi post di stackoverflow. Notare anche come nessuna risposta è stata accettata da Konrad, il che indica che queste risposte non hanno risolto il suo problema. Anche se vedo il tuo punto generale.
A. Steenbergen

-1
 mMessageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
        @Override
        public void onChanged() {
            mLayoutManager.smoothScrollToPosition(mMessageRecycler, null, mMessageAdapter.getItemCount());
        }
    });

La soluzione qui è continuare a scorrere Recyclerview quando arriva un nuovo messaggio.

Il metodo onChanged () rileva l'azione eseguita su recyclerview.


-1

Funziona per me a Kotlin.

  1. Crea l'adattatore e consegna i tuoi dati nel costruttore
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>()  { ... }
  1. cambia questa proprietà e chiama notifyDataSetChanged ()
adapter.currentPole = pole
adapter.notifyDataSetChanged()

L'offset di scorrimento non cambia.


-2

Ho avuto questo problema con un elenco di elementi che avevano ciascuno un tempo in minuti fino a quando non erano "scaduti" e necessitavano di un aggiornamento. Aggiornerei i dati e poi, dopo, chiamo

orderAdapter.notifyDataSetChanged();

e ogni volta scorre verso l'alto. L'ho sostituito con

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

e andava bene. Nessuno degli altri metodi in questo thread ha funzionato per me. Utilizzando questo metodo, tuttavia, ha fatto lampeggiare ogni singolo elemento quando è stato aggiornato, quindi ho dovuto anche inserirlo nel frammento genitore onCreateView

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

-2

Se hai uno o più EditText all'interno di un elemento recyclerview, disabilita l'autofocus di questi, mettendo questa configurazione nella visualizzazione genitore di recyclerview:

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

Ho avuto questo problema quando ho iniziato un'altra attività lanciata da un elemento di revisione del riciclo, quando sono tornato e ho impostato un aggiornamento di un campo in un elemento con notifyItemChanged (posizione) lo scorrimento delle mosse RV, e la mia conclusione è stata che, l'autofocus di EditText Articoli, il codice sopra ha risolto il mio problema.

migliore.


-3

Ritorna semplicemente se oldPosition e position sono le stesse;

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);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.