Seznamy — pokračování

Jaromír Landa
3 min readJul 31, 2020

--

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:

  1. Obnovíme celý seznam — vše překlereslíme
  2. Řekneme, co se změnilo
  3. 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

A co vy, recyklujete?

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

--

--

No responses yet