Все мы пишем приложения и у всех нас есть списки. И самое очевидное решение это RecyclerView. Сама по себе реализация не сложна и писать гайд по RecyclerView уже не актуально. Но есть одно но. Каждый раз когда нам нужен список мы создаем класс, в нем прописываем шаблонный методы, создаем шаблонные классы. Когда у нас 2-3 списка то ничего страшного в этом нет. Но когда их 10 или того более, то этого делать уже не хочется.
И вот столкнувшись с проблемой я начал искать. Нашел одну очень интересную реализацию на Kotlin. Она мне понравилась, но в ней не хватало нескольких элементов. Потратив еще пару часов, я смог доработать его и теперь реализация адаптера занимает несколько строчек. И здесь я хочу поделиться ею с вами.
Первое что нам необходимо сделать это создать адаптер.
Что у нас здесь происходит? Мы создаем параметрезированный адаптер и переопределяем в нем базовые шаблонный методы. Создаем интерфейс параметризированный интерфейс Binder, который должны будут реализовать наши ViewHolder. В абстрактном методе getLayoutId() мы будет задавать наш макет.
После мы создаем Фабрику для наших ViewHolder.
И вот так будет выглядеть реализация этого адаптера во фрагменте.
Все классно, удобно, быстро. Примерно в таком виде я нашел эту реализацию. Но тут я подумал, а как же быть с кликабельными элементами. И вот мое решение.
Для начала создадим интерфейс
И передадим его в наш интерфейс Binder
А в адаптере создадим дополнительный конструктор:
Что в итоге мы имеем, адаптер который создается в 3 строчки и универсальный интерфейс для всех видом элементов. Если же у нас нет необходимости обрабатывать клики, то мы просто напросто не передаем слушатель в конструктор. Но и это еще не все.
А вдруг мы захотим привязать к нашему адаптеру DiffUtils.Callback.
Вот так выглядит базовый класс для наших DiffUtils. Добавляем в наш адаптер метод
И немного модифицируем метод адаптера update()
И вот так мы реализуем наш DiffUtils
В итоге мы имеем простую и достаточно гибкую реализацию шаблонного кода. Удобную реализацию адаптеров с несколькими ViewHolders. Централизованную логику в одном месте.
Здесь есть можно посмотреть исходный код.
А здесь можно посмотреть исходную версию.
И вот столкнувшись с проблемой я начал искать. Нашел одну очень интересную реализацию на Kotlin. Она мне понравилась, но в ней не хватало нескольких элементов. Потратив еще пару часов, я смог доработать его и теперь реализация адаптера занимает несколько строчек. И здесь я хочу поделиться ею с вами.
Первое что нам необходимо сделать это создать адаптер.
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) } }
Что у нас здесь происходит? Мы создаем параметрезированный адаптер и переопределяем в нем базовые шаблонный методы. Создаем интерфейс параметризированный интерфейс Binder, который должны будут реализовать наши ViewHolder. В абстрактном методе getLayoutId() мы будет задавать наш макет.
После мы создаем Фабрику для наших 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 } } }
И вот так будет выглядеть реализация этого адаптера во фрагменте.
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 }
Все классно, удобно, быстро. Примерно в таком виде я нашел эту реализацию. Но тут я подумал, а как же быть с кликабельными элементами. И вот мое решение.
Для начала создадим интерфейс
interface OnItemClickListener<T> { fun onClickItem(data: T) }
И передадим его в наш интерфейс Binder
internal interface Binder<T> { fun bind(data: T, listener: OnItemClickListener<T>?) }
А в адаптере создадим дополнительный конструктор:
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) } } }
Что в итоге мы имеем, адаптер который создается в 3 строчки и универсальный интерфейс для всех видом элементов. Если же у нас нет необходимости обрабатывать клики, то мы просто напросто не передаем слушатель в конструктор. Но и это еще не все.
А вдруг мы захотим привязать к нашему адаптеру DiffUtils.Callback.
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 }
Вот так выглядит базовый класс для наших DiffUtils. Добавляем в наш адаптер метод
private var diffUtil: GenericItemDiff<T>? = null fun setDiffUtilCallback(diffUtilImpl: GenericItemDiff<T>) { diffUtil = diffUtilImpl }
И немного модифицируем метод адаптера 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() } }
И вот так мы реализуем наш 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 }
В итоге мы имеем простую и достаточно гибкую реализацию шаблонного кода. Удобную реализацию адаптеров с несколькими ViewHolders. Централизованную логику в одном месте.
Здесь есть можно посмотреть исходный код.
А здесь можно посмотреть исходную версию.
