Generic Recycler Lihat atau bagaimana tidak menulis kode boilerplate

Kita semua menulis aplikasi dan kita semua punya daftar. Dan solusi yang paling jelas adalah RecyclerView. Implementasinya sendiri tidak rumit dan menulis panduan tentang RecyclerView tidak lagi relevan. Tetapi ada satu hal. Setiap kali kita membutuhkan daftar, kita membuat kelas, meresepkan metode template di dalamnya, membuat kelas template. Ketika kita memiliki 2-3 daftar, maka tidak ada yang salah dengan itu. Tetapi ketika ada 10 atau lebih dari mereka, maka Anda tidak ingin melakukan ini lagi.

Dan dihadapkan dengan masalah, saya mulai mencari. Menemukan satu implementasi yang sangat menarik di Kotlin. Saya menyukainya, tetapi tidak memiliki beberapa elemen. Setelah menghabiskan beberapa jam, saya bisa menyelesaikannya dan sekarang implementasi adaptor membutuhkan beberapa jalur. Dan di sini saya ingin membaginya dengan Anda.

Hal pertama yang perlu kita lakukan adalah membuat adaptor.

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

Apa yang sedang terjadi di sini? Kami membuat adaptor parameter dan mendefinisikan kembali metode template dasar di dalamnya. Kami membuat antarmuka antarmuka Pengikat parameter, yang ViewHolder kami harus menerapkan. Dalam metode getLayoutId () abstrak, kita akan mengatur tata letak kita.

Setelah kami membuat Pabrik untuk ViewHolder kami.

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

Dan inilah bagaimana implementasi adaptor ini dalam fragmen akan terlihat.

 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 } 

Semuanya keren, nyaman, cepat. Dalam kira-kira formulir ini, saya menemukan implementasi ini. Tapi kemudian saya berpikir, bagaimana dengan elemen yang dapat diklik. Dan ini keputusan saya.

Pertama, buat antarmuka

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

Dan meneruskannya ke antarmuka pengikat kami
 internal interface Binder<T> { fun bind(data: T, listener: OnItemClickListener<T>?) } 

Dan di adaptor, buat konstruktor tambahan:

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

Apa yang pada akhirnya kita miliki, adaptor yang dibuat dalam 3 baris dan antarmuka universal untuk semua jenis elemen. Jika kita tidak perlu memproses klik, maka kita tidak perlu meneruskan pendengar ke konstruktor. Tapi itu belum semuanya.

Bagaimana jika kita ingin mengikat DiffUtils.Callback ke adaptor kita.

 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 } 

Beginilah bentuk kelas dasar untuk DiffUtils kami. Tambahkan metode ke adaptor kami

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

Dan sedikit memodifikasi pembaruan metode adaptor ()
  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() } } 

Jadi kami mengimplementasikan DiffUtils kami
 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 } 

Hasilnya, kami memiliki implementasi sederhana dari kode templat. Implementasi adaptor yang mudah dengan beberapa ViewHolders. Logika terpusat di satu tempat.

Di sini Anda dapat melihat kode sumbernya .

Dan di sini Anda dapat melihat versi aslinya .

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


All Articles