Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Commit

Permalink
Move delegates into base interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
Goooler committed Jun 25, 2024
1 parent 74088bb commit 9d32408
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 194 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@ import kotlinx.collections.immutable.toImmutableList
* @since 1.0.0
*/
abstract class BaseRvAdapter<M : IVhModelType> private constructor(
private val delegate: IRvAdapterDelegate.Impl<M, BaseRvAdapter<M>>,
private val delegate: IMutableRvAdapter.Impl<M, BaseRvAdapter<M>>,
) : RecyclerView.Adapter<BindingViewHolder>(),
IRvBinding<M>,
IMutableRvAdapter<M>,
IRvAdapterDelegate<M, BindingViewHolder> by delegate {
IMutableRvAdapter<M, BindingViewHolder> by delegate {

constructor() : this(IRvAdapterDelegate.Impl()) {
constructor() : this(IMutableRvAdapter.Impl()) {
@Suppress("LeakingThis")
delegate.adapter = this
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
package io.goooler.demoapp.adapter.rv.core

import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.databinding.BindingAdapter
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import io.goooler.demoapp.adapter.rv.core.ISpanSize.Companion.SPAN_SIZE_FULL

/**
* Keep the same signature as [RecyclerView.Adapter].
*
* Workaround for [KT-21955](https://youtrack.jetbrains.com/issue/KT-21955).
*/
internal interface RecyclerViewAdapter<VH : RecyclerView.ViewHolder> {
fun onAttachedToRecyclerView(recyclerView: RecyclerView)
fun onDetachedFromRecyclerView(recyclerView: RecyclerView)
fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH
fun onBindViewHolder(holder: BindingViewHolder, position: Int)
fun onBindViewHolder(holder: BindingViewHolder, position: Int, payloads: List<Any>)
fun getItemViewType(position: Int): Int
}

/**
* Created on 2020/10/21.
Expand All @@ -13,7 +32,7 @@ import androidx.recyclerview.widget.RecyclerView
* @version 1.0.0
* @since 1.0.0
*/
internal interface IRvAdapter<M : IVhModelType> {
internal interface IRvAdapter<M : IVhModelType, VH : RecyclerView.ViewHolder> : RecyclerViewAdapter<VH> {

/**
* Get data list.
Expand All @@ -39,9 +58,119 @@ internal interface IRvAdapter<M : IVhModelType> {
* Get item by position.
*/
operator fun get(position: Int): M?

@Suppress("TooManyFunctions")
open class Impl<M : IVhModelType, AP> : IRvAdapter<M, BindingViewHolder>
where AP : IRvAdapter<M, BindingViewHolder>,
AP : IRvBinding<M>,
AP : RecyclerView.Adapter<BindingViewHolder> {

private val ivdManager = ViewTypeDelegateManager<M>()

@Suppress("PropertyName", "ktlint:standard:backing-property-naming")
protected val _list = mutableListOf<M>()

lateinit var adapter: AP

override val list: List<M> get() = _list

override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
initManager(ivdManager)
fixSpanSize(recyclerView)
}

override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
ivdManager.clear()
recyclerView.adapter = null
}

override operator fun get(position: Int): M = _list.getOrElse(position) {
// Override get in adapters as a fallback.
adapter[position] ?: error("No such a element in position $position in adapter $adapter.")
}

@LayoutRes
override fun getItemViewType(position: Int): Int = get(position).viewType

override fun onCreateViewHolder(
parent: ViewGroup,
@LayoutRes viewType: Int,
): BindingViewHolder = BindingViewHolder.create(parent, viewType).also {
adapter.onCreateVHForAll(it.binding)
ivdManager.onCreateVH(it.binding, viewType)
}

override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {
onBindViewHolder(holder, position, emptyList())
}

override fun onBindViewHolder(holder: BindingViewHolder, position: Int, payloads: List<Any>) {
get(position).let {
setFullSpan(holder, it)
adapter.onBindVHForAll(holder.binding, it, payloads)
ivdManager.onBindVH(holder.binding, it, payloads)
holder.binding.executePendingBindings()
}
}

override fun onCreateVHForAll(binding: ViewDataBinding) {
adapter.onCreateVH(binding)
}

override fun onBindVHForAll(binding: ViewDataBinding, model: M, payloads: List<Any>) {
adapter.onBindVH(binding, model, payloads)
}

/**
* Transform data list. Always return a new list.
*/
fun transform(original: List<M>): List<M> {
val result = mutableListOf<M>()
original.forEach { findLeaf(it, result) }
return result
}

/**
* Recursively traversing all leaf nodes.
*/
@Suppress("UNCHECKED_CAST")
private fun findLeaf(model: M, list: MutableList<M>) {
if (model is IVhModelWrapper<*>) {
if (model.viewType != -1) list += model
model.subList.forEach { findLeaf(it as M, list) }
} else {
list += model
}
}

/**
* Fix span size when recyclerView's layoutManager is [GridLayoutManager].
*/
private fun fixSpanSize(recyclerView: RecyclerView) {
(recyclerView.layoutManager as? GridLayoutManager)?.let {
it.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
(get(position) as? ISpanSize)?.spanSize?.let { size ->
return if (size == SPAN_SIZE_FULL) it.spanCount else size
}
return it.spanCount
}
}
}
}

/**
* Set full span when recyclerView's layoutManager is [StaggeredGridLayoutManager].
*/
private fun setFullSpan(holder: RecyclerView.ViewHolder, item: M) {
(holder.itemView.layoutParams as? StaggeredGridLayoutManager.LayoutParams)?.let {
it.isFullSpan = (item as? ISpanSize)?.spanSize == SPAN_SIZE_FULL
}
}
}
}

internal interface IMutableRvAdapter<M : IVhModelType> : IRvAdapter<M> {
internal interface IMutableRvAdapter<M : IVhModelType, VH : BindingViewHolder> : IRvAdapter<M, VH> {

/**
* Set or Get data list.
Expand All @@ -56,16 +185,65 @@ internal interface IMutableRvAdapter<M : IVhModelType> : IRvAdapter<M> {
fun removeItem(index: Int)

fun removeItem(item: M)

class Impl<M : IVhModelType, AP> :
IRvAdapter.Impl<M, AP>(),
IMutableRvAdapter<M, BindingViewHolder>
where AP : IRvAdapter<M, BindingViewHolder>,
AP : IRvBinding<M>,
AP : RecyclerView.Adapter<BindingViewHolder> {

override var list: List<M>
get() = _list
set(value) {
_list.clear()
_list.addAll(transform(value))
}

override fun refreshItems(items: List<M>) {
refreshItems(items, adapter::notifyItemChanged)
}

override fun removeItem(index: Int) {
removeItem(index = index, adapter::notifyItemRemoved)
}

override fun removeItem(item: M) {
removeItem(item = item, adapter::notifyItemRemoved)
}

/**
* Compare the list to find the same items and refresh them.
*/
private inline fun refreshItems(items: List<M>, notify: (Int) -> Unit) {
transform(items).forEach {
if (it in _list) {
notify(_list.indexOf(it))
}
}
}

private inline fun removeItem(index: Int, notify: (Int) -> Unit) {
_list.removeAt(index)
notify(index)
}

private inline fun removeItem(item: M, notify: (Int) -> Unit) {
_list.indexOf(item).takeIf { it != -1 }?.let {
removeItem(it, notify)
}
}
}
}

@BindingAdapter("binding_rv_dataList")
internal fun <M : IVhModelType> RecyclerView.bindingSetList(list: List<M>?) {
@Suppress("UNCHECKED_CAST")
(adapter as? IMutableRvAdapter<M>)?.list = list.orEmpty()
(adapter as? IMutableRvAdapter<M, *>)?.list = list.orEmpty()
}

@BindingAdapter("binding_rv_refreshItems")
internal fun <M : IVhModelType> RecyclerView.bindingRefreshItems(items: List<M>?) {
@Suppress("UNCHECKED_CAST")
(adapter as? IMutableRvAdapter<M>)?.refreshItems(items.orEmpty())
(adapter as? IMutableRvAdapter<M, *>)?.refreshItems(items.orEmpty())
}
Loading

0 comments on commit 9d32408

Please sign in to comment.