Seznamy — pokračování
V předchozí kapitole jsem vytvořili funkční seznam. Ale co dál? Nyní se podívejme na další důležité věci, které musíme u seznamů znát.
Obnova seznamu
Dřív nebo později dojde k tomu, že potřebujeme seznam obnovit. Přišla nám nová data, smazali jsme položku, prostě se změnil seznam položek.
Obnova seznamu se řeší třemi způsoby:
- Obnovíme celý seznam — vše překlereslíme
- Řekneme, co se změnilo
- Použijeme DiffUtils
Obnovíme celý seznam
Jedná se o nejjednodušší variantu. Ve chvíli, kdy se změní seznam položek zavoláme nad adaptérem metodu notifyDataSetChanged().
adapter.notifyDataSetChanged()
Voláním metody dojde k překreslení celého seznamu. Vše se přenačte. Výhodou této metody je jednoduchost. Nevýhodou je paměťová náročnost. Pokud máme složité řádky seznamy, např. obsahující obrázky, dojde k probliknutí položek a změna nebude uživatelsky přívětivá.
Řekneme, co se změnilo
Druhou variantou je, že prostě adaptéru řekneme, co se změnilo. Přidali jsme položku na konec seznamu? Prostě zavoláme:
adapter.notifyItemInserted(list.size-1)
Adaptér má spoustu těchto metod, jak např.:
adapter.notifyItemRemoved()
adapter.notifyItemChanged()
adapter.notifyItemMoved()
adapter.notifyItemRangeChanged()
Výhodou toho přístupu je to, že se načte pouze ta položka/položky, které se změnily. Nevýhodu je náročnější ošetření kódu.
Použití DiffUtils
Co ale v případě, že se toho v seznamu změní hodně? První položka je kompletně nová, třetí je zmazaná a u páte se změnila data? Pak musíme všechny stavy ošetřit. Dříve si programátoři toto ošetření vytvářeli sami. Google na to zareagoval vytvořením třídy DiffUtils. Tato třída automaticky zjistí, co se změnilo a obnoví adaptér.
val diffUtil = DiffUtil.calculateDiff(object : DiffUtil.Callback(){
override fun areItemsTheSame(oldItemPosition: Int,
newItemPosition: Int): Boolean {
return list[oldItemPosition]
.name.equals(newList[newItemPosition].name)
}
override fun areContentsTheSame(oldItemPosition: Int,
newItemPosition: Int): Boolean {
return list[oldItemPosition] ==
list[newItemPosition]
}
override fun getOldListSize() = list.size
override fun getNewListSize() = newList.size
})
Pomocí metod areContentsTheSame a areItemsTheSame řekneme, jestli jsou seznamy, resp. položky na stejné pozici v seznamu stejné. Pokud ano, není nutné položku refreshnout. Jediné co je nutné je identifikovat každý řádek, zde například použijeme name.
V kódu pak jen zavoláme
diffUtil.dispatchUpdatesTo(adapter)
Ale pozor! Jen tento kód sice obnoví seznam, ale náš původní seznam (MutableList) je pořád stejný. Takže uděláme ještě následující:
list.clear()
list.addAll(newList)
A je to. Už umíme seznam obnovit.
Recyklace
RecyclerView, jak už napovídá název recykluje View. To znemená, že recykluje jednotlivé řádky. Proč? Protože je to efektivnější. Nemusíme pořád znovu a znovu vytvářet View, což je hodně náročné na paměť.
Má to ale jednu velkou nevýhodu. Představme si, že chceme, aby každý lichý řádek měl jinou barvu pozadí než sudý. Jak prosté. Do metody onBindViewHolder přidáme následující kód:
if (position % 2 == 0){
holder.itemView.setBackgroundColor(
ContextCompat.getColor(this@MainActivity,
R.color.colorAccent))
}
A je to. Když si to ale zkusíme, uvidíme problém. Podívejte se na následující video:
Na první pohled je vše v pořádku, ale jak scrolujeme dolů a nahoru, vše se pokazí. Důvod je jednoduchý. Než je každý řádek zobrazen, je vytvořena instance třídy ViewHolder, která drží reference na jednotlivá View. Nicméně takhle instance se recykluje, tedy znuvu používá. Když je seznam zobrazen, barvy jsou nastaveny, ale jak scrolujeme dál, někde je nastavíme, ale někde už jsou. Je to tím, že celý ViewHolder je znovu použit.
Plus: Díky recyklaci je scrolování mnohem rychlejší.
Mínus: Musíme to ošetřit.
Řešení je jednoduché. Pokud máme kdekoliv v metodě onBindViewHolder if, přidejme i else.
if (position % 2 == 0){
holder.itemView.setBackgroundColor(
ContextCompat.getColor(this@MainActivity,
R.color.colorAccent))
} else {
holder.itemView.setBackgroundColor(
ContextCompat.getColor(this@MainActivity,
android.R.color.white))
}