Generic Recycler View ou comment ne pas écrire de code passe-partout

Nous écrivons tous des applications et nous avons tous des listes. Et la solution la plus évidente est RecyclerView. La mise en œuvre elle-même n'est pas compliquée et la rédaction d'un guide sur RecyclerView n'est plus pertinente. Mais il y a une chose. Chaque fois que nous avons besoin d'une liste, nous créons une classe, y prescrivons des méthodes de modèle, créons des classes de modèle. Lorsque nous avons 2-3 listes, il n'y a rien de mal à cela. Mais quand il y en a 10 ou plus, vous ne voulez plus faire ça.

Et face à un problème, j'ai commencé à chercher. Trouvé une implémentation très intéressante sur Kotlin. Je l'ai aimé, mais il manquait quelques éléments. Après avoir passé quelques heures, j'ai pu le finaliser et maintenant l'implémentation de l'adaptateur prend plusieurs lignes. Et ici, je veux le partager avec vous.

La première chose que nous devons faire est de créer un adaptateur.

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

Que se passe-t-il ici? Nous créons un adaptateur paramétré et y redéfinissons les méthodes de base du modèle. Nous créons l'interface une interface Binder paramétrée, que notre ViewHolder devra implémenter. Dans la méthode abstraite getLayoutId (), nous allons définir notre disposition.

Après avoir créé une usine pour notre ViewHolder.

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

Et voici à quoi ressemblera l'implémentation de cet adaptateur dans le fragment.

 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 } 

Tout est cool, pratique, rapide. À peu près sous cette forme, j'ai trouvé cette implémentation. Mais alors j'ai pensé, qu'en est-il des éléments cliquables. Et voici ma décision.

Créez d'abord une interface

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

Et passez-le à notre interface de reliure
 internal interface Binder<T> { fun bind(data: T, listener: OnItemClickListener<T>?) } 

Et dans l'adaptateur, créez un constructeur supplémentaire:

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

Ce que nous avons à la fin, un adaptateur qui est créé en 3 lignes et une interface universelle pour tous types d'éléments. Si nous n'avons pas besoin de traiter les clics, nous ne transmettons tout simplement pas l'écouteur au constructeur. Mais ce n'est pas tout.

Et si nous voulons lier DiffUtils.Callback à notre adaptateur.

 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 } 

Voici à quoi ressemble la classe de base de nos DiffUtils. Ajoutez une méthode à notre adaptateur

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

Et modifiez légèrement la mise à jour de la méthode de l'adaptateur ()
  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() } } 

Et donc nous implémentons nos 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 } 

En conséquence, nous avons une implémentation simple et assez flexible du code du modèle. Implémentation pratique d'adaptateurs avec plusieurs ViewHolders. Logique centralisée en un seul endroit.

Ici vous pouvez voir le code source .

Et ici, vous pouvez voir la version originale .

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


All Articles