Ich verwende die neue Support-Bibliothek ListAdapter
. Hier ist mein Code für den Adapter
class ArtistsAdapter : ListAdapter<Artist, ArtistsAdapter.ViewHolder>(ArtistsDiff()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(parent.inflate(R.layout.item_artist))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(artist: Artist) {
itemView.artistDetails.text = artist.artistAlbums
.plus(" Albums")
.plus(" \u2022 ")
.plus(artist.artistTracks)
.plus(" Tracks")
itemView.artistName.text = artist.artistCover
itemView.artistCoverImage.loadURL(artist.artistCover)
}
}
}
Ich aktualisiere den Adapter mit
musicViewModel.getAllArtists().observe(this, Observer {
it?.let {
artistAdapter.submitList(it)
}
})
Meine Diff-Klasse
class ArtistsDiff : DiffUtil.ItemCallback<Artist>() {
override fun areItemsTheSame(oldItem: Artist?, newItem: Artist?): Boolean {
return oldItem?.artistId == newItem?.artistId
}
override fun areContentsTheSame(oldItem: Artist?, newItem: Artist?): Boolean {
return oldItem == newItem
}
}
Was passiert, ist, wenn submitList zum ersten Mal aufgerufen wird, wenn der Adapter alle Elemente rendert, aber wenn submitList mit aktualisierten Objekteigenschaften erneut aufgerufen wird, wird die geänderte Ansicht nicht erneut gerendert.
Es rendert die Ansicht neu, während ich durch die Liste scrolle, was wiederum aufruft bindView()
Außerdem ist mir aufgefallen, dass beim Aufruf adapter.notifyDatasSetChanged()
nach dem Senden der Liste die Ansicht mit aktualisierten Werten gerendert wird. Ich möchte jedoch nicht aufrufen, notifyDataSetChanged()
da im Listenadapter verschiedene Dienstprogramme integriert sind
Kann mir hier jemand helfen?
quelle
ArtistsDiff
und damit mit der Implementierung von sichArtist
selbst zusammenhängen.Antworten:
Edit: Ich verstehe, warum das passiert, das war nicht mein Punkt. Mein Punkt ist, dass es zumindest eine Warnung geben oder die
notifyDataSetChanged()
Funktion aufrufen muss . Weil ich diesubmitList(...)
Funktion anscheinend aus einem bestimmten Grund aufrufe. Ich bin mir ziemlich sicher, dass die Leute versuchen herauszufinden, was stundenlang schief gelaufen ist, bis sie herausgefunden haben, dass submitList () den Aufruf stillschweigend ignoriert.Dies liegt an der
Google
seltsamen Logik. Wenn Sie also dieselbe Liste an den Adapter übergeben, wird nicht einmal der aufgerufenDiffUtil
.public void submitList(final List<T> newList) { if (newList == mList) { // nothing to do return; } .... }
Ich verstehe den ganzen Punkt wirklich nicht,
ListAdapter
wenn es nicht möglich ist, Änderungen an derselben Liste zu verarbeiten. Wenn Sie die Elemente in der Liste ändern möchten, die Sie an übergeben,ListAdapter
und die Änderungen anzeigen möchten, müssen Sie entweder eine tiefe Kopie der Liste erstellen oder sie regelmäßigRecyclerView
mit Ihrer eigenenDiffUtill
Klasse verwenden.quelle
submitList
, richtig? Es sollte zumindest den Anruf anrufen,notifyDataSetChanged()
anstatt den Anruf stillschweigend zu ignorieren. Ich bin mir ziemlich sicher, dass die Leute versuchen herauszufinden, was stundenlang schief gelaufen ist, bis sie herausfinden, dasssubmitList()
der Anruf stillschweigend ignoriert wird.RecyclerView.Adapter<VH>
undnotifyDataSetChanged()
. Das Leben ist jetzt gut. Verschwendete eine gute Anzahl von StundennotifyDataSetChanged()
ist teuer und würde den Punkt einer DiffUtil-basierten Implementierung völlig zunichte machen. Sie können vorsichtig und absichtlich sein, wenn SiesubmitList
nur mit neuen Daten anrufen , aber das ist wirklich nur eine Leistungsfalle.Die Bibliothek geht davon aus, dass Sie Room oder ein anderes ORM verwenden, das bei jeder Aktualisierung eine neue asynchrone Liste bietet. Wenn Sie also nur submitList aufrufen, funktioniert dies. Bei schlampigen Entwicklern wird verhindert, dass die Berechnungen zweimal ausgeführt werden, wenn dieselbe Liste aufgerufen wird.
Die akzeptierte Antwort ist richtig, sie bietet die Erklärung, aber nicht die Lösung.
Wenn Sie keine solchen Bibliotheken verwenden, können Sie Folgendes tun:
submitList(null); submitList(myList);
Eine andere Lösung wäre, die submitList (die nicht so schnell blinkt) als solche zu überschreiben:
@Override public void submitList(final List<Author> list) { super.submitList(list != null ? new ArrayList<>(list) : null); }
Oder mit Kotlin-Code:
override fun submitList(list: List<CatItem>?) { super.submitList(list?.let { ArrayList(it) }) }
Fragwürdige Logik funktioniert aber perfekt. Meine bevorzugte Methode ist die zweite, da nicht jede Zeile einen onBind-Aufruf erhält.
quelle
.submitList(new ArrayList(list))
Mit Kotlin müssen Sie lediglich Ihre Liste in eine neue MutableList wie diese oder eine andere Art von Liste konvertieren, je nach Ihrer Verwendung
.observe(this, Observer { adapter.submitList(it?.toMutableList()) })
quelle
Ich hatte ein ähnliches Problem, aber das falsche Rendern wurde durch eine Kombination von
setHasFixedSize(true)
und verursachtandroid:layout_height="wrap_content"
. Zum ersten Mal wurde der Adapter mit einer leeren Liste geliefert, sodass die Höhe nie aktualisiert wurde und wurde0
. Wie auch immer, dies hat mein Problem behoben. Jemand anderes hat möglicherweise das gleiche Problem und glaubt, dass es sich um ein Problem im Adapter handelt.quelle
Wenn bei der Verwendung einige Probleme auftreten
recycler_view.setHasFixedSize(true)
Sie sollten diesen Kommentar auf jeden Fall überprüfen: https://github.com/thoughtbot/expandable-recycler-view/issues/53#issuecomment-362991531
Es hat das Problem auf meiner Seite gelöst.
(Hier ist ein Screenshot des gewünschten Kommentars)
quelle
Heute bin ich auch auf dieses "Problem" gestoßen. Mit Hilfe der Antwort von insa_c und der Lösung von RJFares habe ich mir eine Kotlin-Erweiterungsfunktion gemacht:
/** * Update the [RecyclerView]'s [ListAdapter] with the provided list of items. * * Originally, [ListAdapter] will not update the view if the provided list is the same as * currently loaded one. This is by design as otherwise the provided DiffUtil.ItemCallback<T> * could never work - the [ListAdapter] must have the previous list if items to compare new * ones to using provided diff callback. * However, it's very convenient to call [ListAdapter.submitList] with the same list and expect * the view to be updated. This extension function handles this case by making a copy of the * list if the provided list is the same instance as currently loaded one. * * For more info see 'RJFares' and 'insa_c' answers on * /programming/49726385/listadapter-not-updating-item-in-reyclerview */ fun <T, VH : RecyclerView.ViewHolder> ListAdapter<T, VH>.updateList(list: List<T>?) { // ListAdapter<>.submitList() contains (stripped): // if (newList == mList) { // // nothing to do // return; // } this.submitList(if (list == this.currentList) list.toList() else list) }
die dann überall verwendet werden kann, zB:
viewModel.foundDevices.observe(this, Observer { binding.recyclerViewDevices.adapter.updateList(it) })
und es kopiert die Liste nur (und immer), wenn sie mit der aktuell geladenen Liste identisch ist.
quelle
Laut den offiziellen Dokumenten :
Jedes Mal, wenn Sie submitList aufrufen , wird eine neue Liste gesendet, die differenziert und angezeigt werden soll.
Aus diesem Grund wird bei jedem Aufruf von submitList in der vorherigen (bereits übermittelten Liste) der Diff nicht berechnet und der Adapter nicht über Änderungen im Dataset benachrichtigt .
quelle
Für mich trat dieses Problem auf, wenn ich
RecyclerView
innerhalb vonScrollView
mitnestedScrollingEnabled="false"
und RV-Höhe auf eingestellt warwrap_content
.Der Adapter wurde ordnungsgemäß aktualisiert und die Bindefunktion wurde aufgerufen, aber die Elemente wurden nicht angezeigt - die
RecyclerView
blieb in seiner ursprünglichen Größe hängen.Ändern,
ScrollView
umNestedScrollView
das Problem zu beheben.quelle
In meinem Fall habe ich vergessen, das
LayoutManager
für das einzustellenRecyclerView
. Der Effekt davon ist der gleiche wie oben beschrieben.quelle
Für jeden, dessen Szenario mit meinem identisch ist, lasse ich hier meine Lösung, von der ich nicht weiß, warum sie funktioniert.
Die Lösung, die für mich funktioniert hat, war von @Mina Samir, die die Liste als veränderbare Liste einreicht.
Mein Problemszenario:
-Laden einer Freundesliste in einem Fragment.
ActivityMain hängt die FragmentFriendList an (beobachtet die Livedata von Friend-DB-Elementen) und fordert gleichzeitig eine http-Anfrage an den Server an, um alle meine Freundeslisten abzurufen.
Aktualisieren oder fügen Sie die Elemente vom http-Server ein.
Jede Änderung löst den onChanged-Rückruf der Livedata aus. Wenn ich die Anwendung zum ersten Mal starte, was bedeutet, dass sich nichts auf meiner Tabelle befand, ist die SubmitList ohne Fehler erfolgreich, aber es wird nichts auf dem Bildschirm angezeigt.
Wenn ich die Anwendung zum zweiten Mal starte, werden Daten auf den Bildschirm geladen.
Die Lösung besteht darin, wie oben beschrieben, die Liste als veränderbare Liste einzureichen.
quelle
Ich hatte ein ähnliches Problem. Das Problem lag in den
Diff
Funktionen, die die Elemente nicht angemessen verglichen. Stellen Sie beiDiff
jedem Problem sicher, dass Ihre Funktionen (und damit auch Ihre Datenobjektklassen) die richtigen Vergleichsdefinitionen enthalten, dh vergleichen Sie alle Felder, die möglicherweise im neuen Element aktualisiert werden. Zum Beispiel im Originalbeitragoverride fun areContentsTheSame(oldItem: Artist?, newItem: Artist?): Boolean { return oldItem == newItem }
Diese Funktion macht (möglicherweise) nicht das, was auf dem Etikett steht: Sie vergleicht nicht den Inhalt der beiden Elemente - es sei denn, Sie haben die
equals()
Funktion in derArtist
Klasse überschrieben . In meinem Fall hatte ich nicht und die Definition vonareContentsTheSame
nur eines der notwendigen Felder überprüft, aufgrund meiner Kontrolle bei der Implementierung. Dies ist strukturelle Gleichheit vs. referentielle Gleichheit. Mehr dazu finden Sie hierquelle
Ich musste meine DiffUtils ändern
override fun areContentsTheSame(oldItem: Vehicle, newItem: Vehicle): Boolean {
Um tatsächlich zurückzugeben, ob der Inhalt neu ist, vergleichen Sie nicht nur die ID des Modells.
quelle
Wenn Sie die erste Antwort von @RJFares verwenden, wird die Liste erfolgreich aktualisiert, der Bildlaufstatus wird jedoch nicht beibehalten. Das Ganze
RecyclerView
startet von der 0. Position. Um dies zu umgehen, habe ich Folgendes getan:fun updateDataList(newList:List<String>){ //new list from DB or Network val tempList = dataList.toMutableList() // dataList is the old list tempList.addAll(newList) listAdapter.submitList(tempList) // Recyclerview Adapter Instance dataList = tempList }
Auf diese Weise kann ich den Bildlaufstatus
RecyclerView
zusammen mit geänderten Daten beibehalten .quelle