Generische Recycler-Ansicht oder wie man keinen Boilerplate-Code schreibt

Wir alle schreiben Bewerbungen und wir haben alle Listen. Die naheliegendste Lösung ist RecyclerView. Die Implementierung selbst ist nicht kompliziert und das Schreiben eines Handbuchs zu RecyclerView ist nicht mehr relevant. Aber es gibt eine Sache. Jedes Mal, wenn wir eine Liste benötigen, erstellen wir eine Klasse, schreiben darin Vorlagenmethoden vor und erstellen Vorlagenklassen. Wenn wir 2-3 Listen haben, ist daran nichts auszusetzen. Wenn es jedoch 10 oder mehr davon gibt, möchten Sie dies nicht mehr tun.

Und angesichts eines Problems begann ich zu suchen. Fand eine sehr interessante Implementierung auf Kotlin. Mir hat es gefallen, aber es fehlten ein paar Elemente. Nachdem ich ein paar Stunden verbracht hatte, konnte ich es fertigstellen und jetzt dauert die Implementierung des Adapters mehrere Zeilen. Und hier möchte ich es mit Ihnen teilen.

Als erstes müssen wir einen Adapter erstellen.

abstract class GenericAdapter<T> : RecyclerView.Adapter<RecyclerView.ViewHolder> { private var itemList = mutableListOf<T>() constructor() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return getViewHolder( LayoutInflater.from(parent.context) .inflate(viewType, parent, false) , viewType ) } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { (holder as Binder<T>).bind(itemList[position], itemClickListener) } override fun getItemCount(): Int = itemList.size override fun getItemViewType(position: Int): Int = getLayoutId(position, itemList[position]) fun update(items: List<T>) { itemList = items.toMutableList() notifyDataSetChanged() } protected abstract fun getLayoutId(position: Int, obj: T): Int protected open fun getViewHolder(view: View, viewType: Int): RecyclerView.ViewHolder { return ViewHolderFactory.create(view, viewType) } internal interface Binder<T> { fun bind(data: T) } } 

Was ist hier los? Wir erstellen einen parametrisierten Adapter und definieren die grundlegenden Vorlagenmethoden darin neu. Wir erstellen für die Schnittstelle eine parametrisierte Binder-Schnittstelle, die unser ViewHolder implementieren muss. In der abstrakten Methode getLayoutId () legen wir unser Layout fest.

Nachdem wir eine Factory für unseren ViewHolder erstellt haben.

 object ViewHolderFactory { fun create(view: View, viewType: Int): RecyclerView.ViewHolder { return when (viewType) { R.layout.item_data -> DataViewHolder(view) R.layout.item_other_data -> OtherDataViewHolder(view) else -> throw Exception("Wrong view type") } } class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), GenericAdapter.Binder<Data> { override fun bind(data: Data) { itemView.apply { dateTextView.text = data.dateTitle } } class OtherDataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), GenericAdapter.Binder<OtherData> { override fun bind(data: OtherData) { itemView.apply { dateTextView.text = data.dateTitle } } } 

Und so wird die Implementierung dieses Adapters im Fragment aussehen.

 private lateinit var adapter GenericAdapter<Data> protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); adapter = dataAdapter } private val dataAdapter = object : GenericAdapter<Data>() { override fun getLayoutId(position: Int, obj: Data): Int = R.layout.item_data } 

Alles ist cool, bequem und schnell. In ungefähr dieser Form fand ich diese Implementierung. Aber dann dachte ich, was ist mit anklickbaren Elementen. Und hier ist meine Entscheidung.

Erstellen Sie zunächst eine Schnittstelle

 interface OnItemClickListener<T> { fun onClickItem(data: T) } 

Und geben Sie es an unsere Binder-Oberfläche weiter
 internal interface Binder<T> { fun bind(data: T, listener: OnItemClickListener<T>?) } 

Erstellen Sie im Adapter einen zusätzlichen Konstruktor:

  private var itemClickListener: OnItemClickListener<T>? = null constructor(listener: OnItemClickListener<T>) { itemClickListener = listener } class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), GenericAdapter.Binder<Data> { override fun bind(data: Data, listener: OnItemClickListener<Data>?) { itemView.apply { dateTextView.text = data.dateTitle setOnClickListener { listener?.onClickItem(data) } } } 

Was wir am Ende haben, ist ein Adapter, der in 3 Zeilen erstellt wird und eine universelle Schnittstelle für alle Arten von Elementen. Wenn wir keine Klicks verarbeiten müssen, übergeben wir den Listener einfach nicht an den Konstruktor. Das ist aber noch nicht alles.

Was ist, wenn wir DiffUtils.Callback an unseren Adapter binden möchten?

 class GenericDiffUtil<T>( private val oldItems: List<T>, private val newItems: List<T>, private val itemDiff: GenericItemDiff<T> ) : DiffUtil.Callback() { override fun getOldListSize() = oldItems.size override fun getNewListSize() = newItems.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = itemDiff.isSame(oldItems, newItems, oldItemPosition, newItemPosition) override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = itemDiff.isSameContent(oldItems, newItems, oldItemPosition, newItemPosition) } interface GenericItemDiff<T> { fun isSame( oldItems: List<T>, newItems: List<T>, oldItemPosition: Int, newItemPosition: Int ): Boolean fun isSameContent( oldItems: List<T>, newItems: List<T>, oldItemPosition: Int, newItemPosition: Int ): Boolean } 

So sieht die Basisklasse für unsere DiffUtils aus. Fügen Sie unserem Adapter eine Methode hinzu

 private var diffUtil: GenericItemDiff<T>? = null fun setDiffUtilCallback(diffUtilImpl: GenericItemDiff<T>) { diffUtil = diffUtilImpl } 

Und ändern Sie leicht die Adaptermethode update ()
  fun update(items: List<T>) { if (diffUtil != null) { val result = DiffUtil.calculateDiff(GenericDiffUtil(itemList, items, diffUtil!!)) itemList.clear() itemList.addAll(items) result.dispatchUpdatesTo(this) } else { itemList = items.toMutableList() notifyDataSetChanged() } } 

Und so implementieren wir unsere DiffUtils
 adapter.setDiffUtilCallback(dataDiffUtil) private val dataDiffUtil = object : GenericItemDiff<Data> { override fun isSame( oldItems: List<Data>, newItems: List<Data>, oldItemPosition: Int, newItemPosition: Int ): Boolean { val oldData = oldItems[oldItemPosition] val newData = newItems[newItemPosition] return oldData.id == newData.id } override fun isSameContent( oldItems: List<Data>, newItems: List<Data>, oldItemPosition: Int, newItemPosition: Int ): Boolean { val oldData = oldItems[oldItemPosition] val newData = newItems[newItemPosition] return oldData.name == newData.name && oldData.content == newData.content } 

Als Ergebnis haben wir eine einfache und ziemlich flexible Implementierung des Vorlagencodes. Bequeme Implementierung von Adaptern mit mehreren ViewHoldern. Zentralisierte Logik an einem Ort.

Hier sehen Sie den Quellcode .

Und hier sehen Sie die Originalversion .

Source: https://habr.com/ru/post/de469557/


All Articles