Created
June 12, 2019 14:45
-
-
Save AlecKazakova/2c5ab7f7f58a5db703d1a7cbd0e1cf9d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.os.Looper | |
import android.view.ViewGroup | |
import androidx.recyclerview.widget.DiffUtil | |
import androidx.recyclerview.widget.RecyclerView | |
import androidx.recyclerview.widget.RecyclerView.Adapter | |
import androidx.recyclerview.widget.RecyclerView.NO_POSITION | |
import androidx.recyclerview.widget.RecyclerView.ViewHolder | |
class ComposableAdapter: Adapter<ViewHolder>() { | |
private val adapterForViewType = LinkedHashMap<Int, Adapter<ViewHolder>>() | |
private val observers = ArrayList<Observer>() | |
private var adapters: List<Adapter<ViewHolder>> = emptyList() | |
set(value) { | |
field.forEachIndexed { index, adapter -> | |
adapter.unregisterAdapterDataObserver(observers[index]) | |
} | |
observers.clear() | |
adapterForViewType.clear() | |
value.fold(0, { priorCount, adapter -> | |
val observer = Observer(priorCount) | |
adapter.registerAdapterDataObserver(observer) | |
observers.add(observer) | |
return@fold priorCount + adapter.itemCount | |
}) | |
val oldValue = field | |
field = value | |
DiffUtil.calculateDiff(object : DiffUtil.Callback() { | |
override fun areItemsTheSame( | |
oldItemPosition: Int, | |
newItemPosition: Int | |
): Boolean { | |
val (oldPrior, oldAdapter) = oldValue.adapterForPosition(oldItemPosition) | |
val (newPrior, newAdapter) = value.adapterForPosition(newItemPosition) | |
val oldPosition = oldItemPosition - oldPrior | |
val newPosition = newItemPosition - newPrior | |
if (oldAdapter.hasStableIds() && newAdapter.hasStableIds()) { | |
return (oldAdapter.getItemViewType(oldPosition) == newAdapter.getItemViewType(newPosition) | |
&& (oldAdapter.getItemId(oldPosition) == newAdapter.getItemId(newPosition))) | |
} | |
return (oldAdapter == newAdapter) && (oldPosition == newPosition) | |
} | |
override fun getOldListSize() = oldValue.sumBy { it.itemCount } | |
override fun getNewListSize() = value.sumBy { it.itemCount } | |
override fun areContentsTheSame( | |
oldItemPosition: Int, | |
newItemPosition: Int | |
) = areItemsTheSame(oldItemPosition, newItemPosition) | |
}).dispatchUpdatesTo(this) | |
} | |
@Suppress("UNCHECKED_CAST") | |
fun setData(adapters: List<Adapter<out ViewHolder>>) { | |
this.adapters = adapters as List<Adapter<ViewHolder>> | |
} | |
private fun List<RecyclerView.Adapter<*>>.adapterForPosition(position: Int): InnerAdapter { | |
fold(0, { total, adapter -> | |
if (total + adapter.itemCount > position) return InnerAdapter(total, adapter) | |
total + adapter.itemCount | |
}) | |
throw IllegalStateException("No adapter for position $position, itemCount: $itemCount, adapters:" + | |
" ${adapters.joinToString { "${it.javaClass.canonicalName}: ${it.itemCount} items" }}") | |
} | |
fun adapterForPosition(position: Int) = adapters.adapterForPosition(position) | |
override fun getItemViewType(position: Int): Int { | |
adapters.fold(0, { total, adapter -> | |
if (total + adapter.itemCount > position) { | |
val viewType = adapter.getItemViewType(position - total) | |
adapterForViewType[viewType] = adapter | |
return viewType | |
} | |
total + adapter.itemCount | |
}) | |
throw IllegalStateException("No viewtype for position $position, itemCount: $itemCount, adapters:" + | |
" ${adapters.joinToString { "${it.javaClass.canonicalName}: ${it.itemCount} items" }}") | |
} | |
override fun onCreateViewHolder( | |
parent: ViewGroup, | |
viewType: Int | |
): ViewHolder { | |
return adapterForViewType[viewType]?.onCreateViewHolder(parent, viewType) | |
?: throw NullPointerException("No adapter for view type $viewType") | |
} | |
override fun getItemCount(): Int { | |
return adapters.sumBy { it.itemCount } | |
} | |
override fun getItemId(position: Int): Long { | |
if (!hasStableIds()) { | |
return super.getItemId(position) | |
} | |
adapters.fold(0, { total, adapter -> | |
if (total + adapter.itemCount > position) { | |
return adapter.getItemId(position - total) | |
} | |
total + adapter.itemCount | |
}) | |
throw IllegalStateException() | |
} | |
override fun onBindViewHolder( | |
holder: ViewHolder, | |
position: Int | |
) { | |
adapters.fold(0, { total, adapter -> | |
if (total + adapter.itemCount > position) { | |
adapter.onBindViewHolder(holder, position - total) | |
return | |
} | |
total + adapter.itemCount | |
}) | |
throw IllegalStateException() | |
} | |
private inner class Observer(private var priorCount: Int) : RecyclerView.AdapterDataObserver() { | |
private fun checkLooper() { | |
if (Looper.myLooper() != Looper.getMainLooper()) { | |
throw IllegalStateException("Can only notify on main thread") | |
} | |
} | |
override fun onChanged() { | |
checkLooper() | |
notifyDataSetChanged() | |
} | |
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) { | |
checkLooper() | |
notifyItemRangeChanged(positionStart + priorCount, itemCount) | |
} | |
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) { | |
checkLooper() | |
notifyItemRangeChanged(positionStart + priorCount, itemCount, payload) | |
} | |
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { | |
checkLooper() | |
observers.drop(observers.indexOf(this) + 1).forEach { | |
it.priorCount += itemCount | |
} | |
notifyItemRangeInserted(positionStart + priorCount, itemCount) | |
} | |
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { | |
checkLooper() | |
notifyDataSetChanged() | |
} | |
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { | |
checkLooper() | |
observers.drop(observers.indexOf(this) + 1).forEach { | |
it.priorCount -= itemCount | |
} | |
notifyItemRangeRemoved(positionStart + priorCount, itemCount) | |
} | |
} | |
data class InnerAdapter( | |
val preceedingItems: Int, | |
val adapter: RecyclerView.Adapter<*> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment