Skip to content

Instantly share code, notes, and snippets.

@felipefpx
Last active May 15, 2020 14:41
Show Gist options
  • Save felipefpx/424d8628fccb515ce0f79da3bb2ea3e2 to your computer and use it in GitHub Desktop.
Save felipefpx/424d8628fccb515ce0f79da3bb2ea3e2 to your computer and use it in GitHub Desktop.
AnyRvAdapter - Recycler view adapter for any class that supports multiple view types
package me.porge.something.adapters
import android.support.v7.widget.RecyclerView
import android.view.View
import android.view.ViewGroup
import kotlin.reflect.KClass
class AnyRvAdapter(
private val controllers: Map<KClass<*>, AnyRvItemController<*>>,
initialItems: List<Any> = emptyList(),
private val onTouchEvent: (TouchEvent, Any) -> Boolean = { _, _ -> false }
) : RecyclerView.Adapter<AnyRvAdapter.ViewHolder>() {
private val _items: MutableList<Any> = initialItems.toMutableList()
@Suppress("UNCHECKED_CAST", "TYPE_INFERENCE_ONLY_INPUT_TYPES_WARNING", "unused")
private val <T : Any> T.controller
get() = tryOrNull { controllers[this::class] }
override fun onCreateViewHolder(parent: ViewGroup, position: Int): ViewHolder =
(_items.getOrNull(position)?.controller?.createView(parent)
?: View(parent.context))
.let(::ViewHolder)
override fun getItemCount(): Int = _items.size
override fun getItemViewType(position: Int): Int = position
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
_items.getOrNull(position)?.let { item ->
holder.bindRootListeners(item)
item.controller?.safeBind(holder.rootView, item, onTouchEvent)
}
}
private fun ViewHolder.bindRootListeners(item: Any) {
rootView.apply {
setOnClickListener {
this@AnyRvAdapter.onTouchEvent(TouchEvent.RootClick, item)
}
setOnLongClickListener {
this@AnyRvAdapter.onTouchEvent(TouchEvent.RootLongClick, item)
}
}
}
fun add(item: Any) {
add(listOf(item))
}
fun add(items: List<Any>) {
_items.let { oldList ->
_items.addAll(items)
notifyItemRangeInserted(oldList.size, oldList.size + items.size - 1)
}
}
class ViewHolder(val rootView: View) : RecyclerView.ViewHolder(rootView)
interface TouchEvent {
object RootClick : TouchEvent
object RootLongClick : TouchEvent
}
}
abstract class AnyRvItemController<T : Any>() {
abstract fun createView(parent: ViewGroup): View
abstract fun bind(
rootView: View,
item: T,
onTouchEvent: (AnyRvAdapter.TouchEvent, T) -> Boolean
)
@Suppress("UNCHECKED_CAST")
internal fun safeBind(
rootView: View,
item: Any,
onTouchEvent: (AnyRvAdapter.TouchEvent, T) -> Boolean
) {
tryOrNull { bind(rootView, item as T, onTouchEvent) }
}
}
fun <T> tryOrNull(block: () -> T) =
try {
block()
} catch(exception: Exception) {
if (BuildConfig.DEBUG) exception.printStackTrace()
null
}
@felipefpx
Copy link
Author

Usage example:

// ...
    override fun showBooks(books: List<PBook>) {
        recyclerBooksList.apply {
            layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
            adapter =
                AnyRvAdapter(
                    mapOf(PBook::class to BookRvItemController),
                    books
                ) { touchEvent, item ->
                    // ...
                    true
                }
        }
    }
}

object BookRvItemController : AnyRvItemController<PBook>() {

    override fun createView(parent: ViewGroup): View =
        LayoutInflater.from(parent.context).inflate(R.layout.item_book, parent, false)

    override fun bind(
        rootView: View,
        item: PBook,
        onTouchEvent: (AnyRvAdapter.TouchEvent, PBook) -> Boolean
    ) {
        with(rootView) {
            txtBookItemTitle.text = item.title
            txtBookItemPublisherName.text = item.publisherName
            txtBookItemAuthorName.text = item.authorName
            sdvBookItemCover.loadImgUrl(item.coverUrl, R.color.gray_light)
        }
    }
}

class PBook(
    val title: String,
    val publisherName: String,
    val authorName: String,
    val coverUrl: String?
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment