Używam nowej biblioteki pomocy ListAdapter
. Oto mój kod adaptera
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)
}
}
}
Aktualizuję adapter za pomocą
musicViewModel.getAllArtists().observe(this, Observer {
it?.let {
artistAdapter.submitList(it)
}
})
Moja klasa różnicowa
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
}
}
Dzieje się tak, gdy funkcja submitList jest wywoływana przy pierwszym renderowaniu przez adapter wszystkich elementów, ale ponowne wywołanie metody submitList ze zaktualizowanymi właściwościami obiektu nie powoduje ponownego renderowania zmienionego widoku.
Ponownie renderuje widok, gdy przewijam listę, która z kolei wywołuje bindView()
Zauważyłem również, że wywołanie adapter.notifyDatasSetChanged()
po przesłaniu listy renderuje widok ze zaktualizowanymi wartościami, ale nie chcę wywoływać, notifyDataSetChanged()
ponieważ adapter listy ma wbudowane narzędzia różnicowe
Czy ktoś mógłby mi tu pomóc?
android
kotlin
listadapter
Veeresh Charantimath
źródło
źródło
ArtistsDiff
z realizacjąArtist
samego siebie.Odpowiedzi:
Edycja: Rozumiem, dlaczego tak się dzieje, nie o to mi chodziło. Chodzi mi o to, że przynajmniej musi dać ostrzeżenie lub wywołać
notifyDataSetChanged()
funkcję. Ponieważ najwyraźniej wywołuję tęsubmitList(...)
funkcję z jakiegoś powodu. Jestem prawie pewien, że ludzie godzinami próbują dowiedzieć się, co poszło nie tak, dopóki nie zorientują się, że submitList () po cichu ignoruje wywołanie.To z powodu
Google
dziwnej logiki. Więc jeśli przekażesz tę samą listę do adaptera, to nawet nie wywołaDiffUtil
.public void submitList(final List<T> newList) { if (newList == mList) { // nothing to do return; } .... }
Naprawdę nie rozumiem całego tego sensu,
ListAdapter
jeśli nie obsługuje zmian na tej samej liście. Jeśli chcesz zmienić elementy na liście, którą przekazujesz,ListAdapter
i zobaczyć zmiany, albo musisz utworzyć głęboką kopię listy, albo musisz używać zwykłegoRecyclerView
z własnąDiffUtill
klasą.źródło
submitList
, prawda? Powinien przynajmniej wywołać połączenienotifyDataSetChanged()
zamiast po cichu ignorować połączenie. Jestem prawie pewien, że ludzie godzinami próbują dowiedzieć się, co poszło nie tak, dopóki nie zorientują się, żesubmitList()
po cichu ignoruje telefon.RecyclerView.Adapter<VH>
inotifyDataSetChanged()
. Życie jest teraz dobre. Zmarnowałem sporo godzinnotifyDataSetChanged()
jest drogi i całkowicie pokonałby sens posiadania implementacji opartej na DiffUtil. Możesz być ostrożny i uważny, dzwoniącsubmitList
tylko z nowymi danymi, ale tak naprawdę to tylko pułapka wydajności.Biblioteka zakłada, że używasz Room lub innego ORM, który oferuje nową listę asynchroniczną za każdym razem, gdy jest aktualizowana, więc samo wywołanie submitList na niej zadziała, a dla niechlujnych programistów zapobiega dwukrotnemu wykonywaniu obliczeń, jeśli zostanie wywołana ta sama lista.
Przyjęta odpowiedź jest prawidłowa, zawiera wyjaśnienie, ale nie zawiera rozwiązania.
Jeśli nie używasz takich bibliotek, możesz:
submitList(null); submitList(myList);
Innym rozwiązaniem byłoby zastąpienie submitList (co nie powoduje tak szybkiego migania) jako takiego:
@Override public void submitList(final List<Author> list) { super.submitList(list != null ? new ArrayList<>(list) : null); }
Lub z kodem Kotlin:
override fun submitList(list: List<CatItem>?) { super.submitList(list?.let { ArrayList(it) }) }
Wątpliwa logika, ale działa doskonale. Moją preferowaną metodą jest druga, ponieważ nie powoduje ona, że każdy wiersz otrzymuje wywołanie onBind.
źródło
.submitList(new ArrayList(list))
z Kotlinem wystarczy przekonwertować swoją listę na nową MutableList, taką jak ta lub inny typ listy zgodnie z Twoim użyciem
.observe(this, Observer { adapter.submitList(it?.toMutableList()) })
źródło
Miałem podobny problem, ale nieprawidłowe renderowanie było spowodowane kombinacją
setHasFixedSize(true)
iandroid:layout_height="wrap_content"
. Po raz pierwszy adapter został dostarczony z pustą listą, więc wysokość nigdy nie była aktualizowana i była0
. W każdym razie to rozwiązało mój problem. Ktoś inny może mieć ten sam problem i pomyśli, że problem dotyczy adaptera.źródło
Jeśli napotkasz problemy podczas używania
recycler_view.setHasFixedSize(true)
zdecydowanie powinieneś sprawdzić ten komentarz: https://github.com/thoughtbot/expandable-recycler-view/issues/53#issuecomment-362991531
To rozwiązało problem po mojej stronie.
(Oto zrzut ekranu z żądanym komentarzem)
źródło
Dziś też natknąłem się na ten „problem”. Z pomocą odpowiedź insa_c za i rozwiązania RJFares koszulka Zrobiłem sobie funkcję przedłużacza Kotlin:
/** * 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) }
które można następnie wykorzystać w dowolnym miejscu, np .:
viewModel.foundDevices.observe(this, Observer { binding.recyclerViewDevices.adapter.updateList(it) })
i tylko (i zawsze) kopiuje listę, jeśli jest taka sama jak aktualnie załadowana.
źródło
Według oficjalnych dokumentów :
Za każdym razem, gdy wywołujesz submitList , przesyła nową listę do porównania i wyświetlenia.
Dlatego za każdym razem, gdy wywołujesz submitList na poprzedniej (już przesłanej liście), nie oblicza Diff i nie powiadamia adaptera o zmianie w zestawie danych.
źródło
U mnie ten problem pojawił się, gdy korzystałem z
RecyclerView
wnętrzaScrollView
znestedScrollingEnabled="false"
i wysokości kampera ustawionej nawrap_content
.Adapter został poprawnie zaktualizowany i wywołano funkcję bind, ale elementy nie zostały wyświetlone - karta
RecyclerView
utknęła w swoim oryginalnym rozmiarze.Zmiana,
ScrollView
abyNestedScrollView
rozwiązać problem.źródło
W moim przypadku zapomniałem ustawić
LayoutManager
dlaRecyclerView
. Efekt jest taki sam, jak opisano powyżej.źródło
Dla każdego, kto ma taki sam scenariusz jak mój, zostawiam tutaj swoje rozwiązanie, którego nie wiem, dlaczego działa.
Rozwiązaniem, które zadziałało, było dla mnie @Mina Samir, która przesyła listę jako zmienną listę.
Mój scenariusz problemu:
-Ładowanie listy znajomych wewnątrz fragmentu.
ActivityMain dołącza FragmentFriendList (obserwuje dane z listy znajomych w bazie danych) i jednocześnie wysyła żądanie http do serwera, aby pobrać całą moją listę znajomych.
Zaktualizuj lub wstaw elementy z serwera http.
Każda zmiana zapala wywołanie zwrotne onChanged liveata. Ale kiedy uruchamiam aplikację po raz pierwszy, co oznacza, że na moim stole nic nie było, lista submitList powiodła się bez żadnego błędu, ale nic nie pojawia się na ekranie.
Jednak gdy uruchamiam aplikację po raz drugi, dane są ładowane na ekran.
Rozwiązaniem jest, jak wspomniano powyżej, przesłanie listy jako mutableList.
źródło
Miałem podobny problem. Problem dotyczył
Diff
funkcji, które nie porównywały odpowiednio elementów. Każdy, kto ma ten problem, upewnij się, że twojeDiff
funkcje (a co za tym idzie, klasy obiektów danych) zawierają właściwe definicje porównawcze - tj. Porównując wszystkie pola, które mogą zostać zaktualizowane w nowej pozycji. Na przykład w oryginalnym pościeoverride fun areContentsTheSame(oldItem: Artist?, newItem: Artist?): Boolean { return oldItem == newItem }
Ta funkcja (potencjalnie) nie robi tego, co jest napisane na etykiecie: nie porównuje zawartości dwóch elementów - chyba że nadpisałeś
equals()
funkcję wArtist
klasie. W moim przypadku tego nie zrobiłem, a definicjęareContentsTheSame
sprawdziłem tylko w jednym z niezbędnych pól, ze względu na mój niedopatrzenie przy jego wdrażaniu. To jest równość strukturalna kontra równość referencyjna, więcej na ten temat można znaleźć tutajźródło
Musiałem zmodyfikować moje DiffUtils
override fun areContentsTheSame(oldItem: Vehicle, newItem: Vehicle): Boolean {
Aby faktycznie zwrócić, czy zawartość jest nowa, a nie tylko porównać identyfikator modelu.
źródło
Użycie @RJFares pierwszej odpowiedzi aktualizuje listę pomyślnie, ale nie utrzymuje stanu przewijania. Całość
RecyclerView
zaczyna się od 0 pozycji. Aby obejść ten problem, zrobiłem to: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 }
W ten sposób jestem w stanie utrzymać stan przewijania
RecyclerView
wraz ze zmodyfikowanymi danymi.źródło