Aktualisieren von Daten in RecyclerView und Beibehalten der Bildlaufposition

96

Wie aktualisiert man die angezeigten Daten RecyclerView(ruft notifyDataSetChangedden Adapter auf) und stellt sicher, dass die Bildlaufposition genau auf die Position zurückgesetzt wird, an der sie war?

Im Falle eines guten alten ist ListViewalles, was es braucht, abzurufen getChildAt(0), zu überprüfen getTop()und anschließend setSelectionFromTopmit denselben genauen Daten aufzurufen .

Im Falle von scheint es nicht möglich zu sein RecyclerView.

Ich denke, ich sollte seine verwenden, LayoutManagerdie zwar bietet scrollToPositionWithOffset(int position, int offset), aber wie kann man die Position und den Versatz richtig abrufen?

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

Oder gibt es eine elegantere Möglichkeit, die Arbeit zu erledigen?

Konrad Morawski
quelle
Es geht um Breiten- und Höhenwerte Ihres RecyclerView oder des LayoutManager. Überprüfen Sie diese Lösung: stackoverflow.com/questions/28993640/…
serefakyuz
@Konard, In meinem Fall habe ich eine Liste von Audiodateien mit Seekbar-Implementierung, die mit folgendem Problem ausgeführt werden: 1) Für wenige zuletzt indizierte Elemente, sobald sie notifyItemChanged (pos) ausgelöst haben, wird die Ansicht unten automatisch verschoben. 2) Während Sie auf notifyItemChanged (pos) bleiben, bleibt die Liste hängen und es ist nicht möglich, einfach zu scrollen. Ich werde zwar eine Frage stellen, aber lassen Sie mich bitte wissen, ob Sie einen besseren Ansatz zur Behebung dieses Problems haben.
CoDe

Antworten:

139

Ich benutze diesen. ^ _ ^

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

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

Es ist einfacher, hoffe es wird dir helfen!

DawnYu
quelle
1
Dies ist eigentlich die richtige Antwort imo. Es wird nur wegen des asynchronen Ladens von Daten schwierig, aber Sie können die Wiederherstellung verschieben.
EpicPandaForce
perfekt +2 genau das, wonach ich gesucht habe
Adeel Turk
@BubbleTree paste.ofcode.org/EEdjSLQbLrcQyxXpBpEQ4m Ich versuche dies zu implementieren, aber es funktioniert nicht. Was mache ich falsch?
Kaustubh Bhagwat
@KaustubhBhagwat Vielleicht sollten Sie "recyclerViewState! = Null" überprüfen, bevor Sie es verwenden.
DawnYu
4
Wo genau soll ich diesen Code verwenden? in der onResume-Methode?
Lucky_girl
47

Ich habe ein ziemlich ähnliches Problem. Und ich habe folgende Lösung gefunden.

Verwenden notifyDataSetChangedist eine schlechte Idee. Sie sollten genauer sein und dann RecyclerViewden Bildlaufstatus für Sie speichern.

Wenn Sie beispielsweise nur eine Aktualisierung benötigen oder mit anderen Worten möchten, dass jede Ansicht neu gebunden wird, gehen Sie wie folgt vor:

adapter.notifyItemRangeChanged(0, adapter.getItemCount());
Kaerdan
quelle
2
Ich habe versucht, adapter.notifyItemRangeChanged (0, adapter.getItemCount ());, das funktioniert auch als .notifyDataSetChanged ()
Mani
4
Das hat in meinem Fall geholfen. Ich habe mehrere Elemente an Position 0 notifyDataSetChangedeingefügt und mit der Bildlaufposition würde sich die Anzeige der neuen Elemente ändern. Wenn verwendet jedoch notifyItemRangeInserted(0, newItems.size() - 1)das RecyclerViewhält den Scroll - Zustand.
Carlosph
sehr sehr gute Antwort, ich suche die Lösung dieser Antwort einen ganzen Tag. Vielen Dank ..
Gundu Bandgar
adapter.notifyItemRangeChanged (0, adapter.getItemCount ()); Sollte vermieden werden, wie in der neuesten Google-E / A angegeben (siehe Präsentation zu Ein- und Ausgängen von Recyclerview), ist es besser (wenn Sie über das Wissen verfügen), dem Recyclerview genau mitzuteilen, welche Elemente geändert wurden, anstatt eine flache Gesamtaktualisierung durchzuführen aller Ansichten.
A. Steenbergen
3
Es funktioniert nicht, die Recycling-Ansicht wird nach oben
aktualisiert,
21

BEARBEITEN: Um genau die gleiche scheinbare Position wie in wiederherzustellen , damit sie genau so aussieht wie sie, müssen wir etwas anderes tun (siehe unten, wie der genaue scrollY-Wert wiederhergestellt wird):

Speichern Sie die Position und den Versatz wie folgt:

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

Und stellen Sie dann die Schriftrolle wie folgt wieder her:

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

Dadurch wird die Liste an ihrer exakten scheinbaren Position wiederhergestellt . Anscheinend, weil es für den Benutzer gleich aussieht, aber nicht den gleichen scrollY-Wert hat (wegen möglicher Unterschiede in den Abmessungen des Quer- / Hochformat-Layouts).

Beachten Sie, dass dies nur mit LinearLayoutManager funktioniert.

--- Nachfolgend erfahren Sie, wie Sie den exakten Bildlauf wiederherstellen, wodurch die Liste wahrscheinlich anders aussieht ---

  1. Wenden Sie einen OnScrollListener wie folgt an:

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

Dadurch wird die genaue Bildlaufposition jederzeit in mScrollY gespeichert.

  1. Speichern Sie diese Variable in Ihrem Bundle und stellen Sie sie in der Statuswiederherstellung in einer anderen Variablen wieder her . Wir nennen sie mStateScrollY.

  2. Nach der Wiederherstellung des Status und nachdem Ihr RecyclerView alle Daten zurückgesetzt hat, setzen Sie den Bildlauf folgendermaßen zurück:

    mRecyclerView.scrollBy(0, mStateScrollY);

Das ist es.

Beachten Sie, dass Sie den Bildlauf in einer anderen Variablen wiederherstellen. Dies ist wichtig, da der OnScrollListener mit .scrollBy () aufgerufen wird und anschließend mScrollY auf den in mStateScrollY gespeicherten Wert setzt. Wenn Sie dies nicht tun, hat mScrollY den doppelten Bildlaufwert (da der OnScrollListener mit Deltas arbeitet, nicht mit absoluten Bildläufen).

Staatliche Einsparungen bei Aktivitäten können wie folgt erreicht werden:

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

Und um es wiederherzustellen, rufen Sie dies in Ihrem onCreate () auf:

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

Das Speichern von Zuständen in Fragmenten funktioniert auf ähnliche Weise, aber das Speichern von Zuständen erfordert ein wenig zusätzliche Arbeit, aber es gibt viele Artikel, die sich damit befassen. Sie sollten also kein Problem damit haben, herauszufinden, wie die Prinzipien zum Speichern des scrollY funktionieren und die Wiederherstellung bleiben gleich.

A. Steenbergen
quelle
Ich verstehe nicht, können Sie ein vollständiges Beispiel posten?
Dies ist tatsächlich so nah wie möglich an einem vollständigen Beispiel, wenn Sie nicht möchten, dass ich meine gesamte Aktivität
poste
Ich verstehe nichts Wenn am Anfang der Liste Elemente hinzugefügt wurden, ist es dann nicht falsch, an derselben Bildlaufposition zu bleiben, da Sie jetzt tatsächlich nach verschiedenen Elementen suchen, die diesen Bereich übernommen haben?
Android-Entwickler
Ja, in diesem Fall müssen Sie die Position beim Wiederherstellen anpassen. Dies ist jedoch nicht Teil der Frage, da Sie beim Drehen normalerweise keine Elemente hinzufügen oder entfernen. Das wäre seltsam und verhaltensmäßig und wahrscheinlich nicht im Interesse des Benutzers, außer für einige Sonderfälle, die existieren könnten, die ich mir ehrlich gesagt nicht vorstellen kann.
A. Steenbergen
9

Ja, Sie können dieses Problem beheben, indem Sie den Adapterkonstruktor nur einmal erstellen. Ich erkläre den Codierungsteil hier:

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

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

Jetzt können Sie sehen, dass ich überprüft habe, ob der Adapter null ist oder nicht, und erst initialisiere, wenn er null ist.

Wenn der Adapter nicht null ist, kann ich sicher sein, dass ich meinen Adapter mindestens einmal initialisiert habe.

Also werde ich einfach eine Liste zum Adapter hinzufügen und notifydatasetchanged aufrufen.

RecyclerView hält immer die letzte gescrollte Position, daher müssen Sie nicht die letzte Position speichern. Rufen Sie einfach notifydatasetchanged auf. Die Recycler-Ansicht aktualisiert immer Daten, ohne nach oben zu gehen.

Vielen Dank Happy Coding

Amit Singh Tomar
quelle
Ich denke, das geht zur akzeptierten Antwort @Konrad Morawski
Jithin Jude
5

Behalten Sie die Bildlaufposition bei, indem Sie die Antwort @DawnYu verwenden, um notifyDataSetChanged()wie folgt zu verpacken :

val recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState() 
adapter.notifyDataSetChanged() 
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)
Morten Holmgaard
quelle
perfekt ... die beste Antwort
jlandyr
Ich habe lange nach dieser Antwort gesucht. vielen Dank.
Alaa AbuZarifa
1

Die Top-Antwort von @DawnYu funktioniert, aber die Recycling-Ansicht scrollt zuerst nach oben und kehrt dann zur beabsichtigten Scroll-Position zurück, was zu einer "flimmerartigen" Reaktion führt, die nicht angenehm ist.

Um die recyclerView zu aktualisieren, insbesondere nachdem Sie von einer anderen Aktivität gekommen sind, ohne zu flackern und die Bildlaufposition beizubehalten, müssen Sie die folgenden Schritte ausführen.

  1. Stellen Sie sicher, dass Sie Ihre Recycler-Ansicht mit DiffUtil aktualisieren. Lesen Sie hier mehr darüber: https://www.journaldev.com/20873/android-recyclerview-diffutil
  2. Wenn Sie Ihre Aktivität fortsetzen oder an dem Punkt, an dem Sie Ihre Aktivität aktualisieren möchten, Daten in Ihre Recycling-Ansicht laden. Mit diffUtil werden nur die Aktualisierungen in der Recyclingansicht vorgenommen, während die Position beibehalten wird.

Hoffe das hilft.

Moses Mwongela
quelle
0

Ich habe Recyclerview nicht verwendet, aber ich habe es in ListView gemacht. Beispielcode in Recyclerview:

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

Es ist der Listener, wenn der Benutzer einen Bildlauf durchführt. Der Leistungsaufwand ist nicht signifikant. Und die erste sichtbare Position ist auf diese Weise genau.

Das Original Android
quelle
Okay, findFirstVisibleItemPositiongibt "die Adapterposition der ersten sichtbaren Ansicht" zurück, aber was ist mit dem Versatz?
Konrad Morawski
1
Gibt es eine ähnliche Methode für Recyclerview wie die setSelection-Methode von ListView für die sichtbare Position? Das habe ich für ListView gemacht.
Das Original Android
1
Wie wäre es mit der setScrollY-Methode mit dem dy-Parameter, einem Wert, den Sie speichern werden? Wenn es funktioniert, werde ich meine Antwort aktualisieren. Ich habe Recyclerview nicht verwendet, möchte es aber in meiner zukünftigen App.
Das Original Android
Bitte lesen Sie meine Antwort unten, wie Sie die Bildlaufposition speichern können.
A. Steenbergen
1
Okay, ich werde das nächste Mal daran denken, ich habe dich nicht mit Vorsatz herabgestimmt. Ich bin jedoch nicht mit kurzen und unvollständigen Antworten einverstanden, da mir die kurzen Antworten, bei denen andere Entwickler viele Hintergrundinformationen annahmen, nicht wirklich geholfen haben, da sie oft zu unvollständig waren und ich viele klärende Fragen stellen musste, etwas, das Sie Dies ist normalerweise bei alten Stackoverflow-Posts nicht möglich. Beachten Sie auch, dass Konrad keine Antwort akzeptiert hat, was darauf hinweist, dass diese Antworten sein Problem nicht gelöst haben. Obwohl ich Ihren allgemeinen Punkt sehe.
A. Steenbergen
-1
 mMessageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
        @Override
        public void onChanged() {
            mLayoutManager.smoothScrollToPosition(mMessageRecycler, null, mMessageAdapter.getItemCount());
        }
    });

Die Lösung hier besteht darin, die Recycling-Ansicht weiter zu scrollen, wenn eine neue Nachricht eingeht.

Die onChanged () -Methode erkennt die in recyclerview ausgeführte Aktion.

Abhishek Chudekar
quelle
-1

Das funktioniert bei mir in Kotlin.

  1. Erstellen Sie den Adapter und übergeben Sie Ihre Daten im Konstruktor
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>()  { ... }
  1. Ändern Sie diese Eigenschaft und rufen Sie notifyDataSetChanged () auf.
adapter.currentPole = pole
adapter.notifyDataSetChanged()

Der Bildlaufversatz ändert sich nicht.

Chris8447
quelle
-2

Ich hatte dieses Problem mit einer Liste von Elementen, die jeweils eine Zeit in Minuten hatten, bis sie "fällig" waren und aktualisiert werden mussten. Ich würde die Daten aktualisieren und danach anrufen

orderAdapter.notifyDataSetChanged();

und es würde jedes Mal nach oben scrollen. Ich habe das durch ersetzt

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

und es war in Ordnung. Keine der anderen Methoden in diesem Thread hat bei mir funktioniert. Bei Verwendung dieser Methode wurde jedoch jedes einzelne Element beim Aktualisieren blinken gelassen, sodass ich dies auch in die onCreateView des übergeordneten Fragments einfügen musste

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

Wenn Sie einen oder mehrere EditTexts in einem Recyclerview-Element haben, deaktivieren Sie den Autofokus dieser Elemente und fügen Sie diese Konfiguration in die übergeordnete Ansicht von recyclerview ein:

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

Ich hatte dieses Problem, als ich eine andere Aktivität startete, die von einem Recyclerview-Element aus gestartet wurde. Als ich zurückkam und eine Aktualisierung eines Felds in einem Element mit notifyItemChanged (Position) festlegte, bewegt sich der Bildlauf von RV, und meine Schlussfolgerung war, dass der Autofokus von EditText Artikel, der obige Code hat mein Problem gelöst.

Beste.

Akhha8
quelle
-3

Kehren Sie einfach zurück, wenn die alte Position und Position identisch sind.

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
quelle