Android RecyclerView With Different Layout / View Type (Kotlin)

July 23, 2018

Declare classes for different layout / view type. I have two different layout classes: Image and Property, where both implement resource: Int to store the layout id.

sealed class ViewItem(val resource: Int) {
    class Image(val imageResource: String): ViewItem(R.layout.content_pin_list_item_image)
    class Property(val icon: Int?,
                   val value: String,
                   val onClick: (() -> Unit)? = null,
                   val onInit: ((viewHolder: LocalListAdapter.ViewHolder) -> Unit)? = null): ViewItem(R.layout.content_pin_list_item)
}

NOTE: If you don’t like sealed class, you could use abstract or interface.

Declare the adapter class.

  • getItemViewType return layout id, which is used in onCreateViewHolder to inflate layout by viewType.
  • I use LayoutContainer Support to enable Android Extensions plugin supports for ViewHolder. You can access view by id using holder.<VIEW_ID> syntax.
class LocalListAdapter() : RecyclerView.Adapter<LocalListAdapter.ViewHolder>() {
    private var items: List<ViewItem> = listOf()

    override fun getItemCount(): Int {
        return items.size
    }

    override fun getItemViewType(position: Int): Int {
        return items[position].resource
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context)
                .inflate(viewType, parent, false)

        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = items[position]
        val context = holder.containerView.context

        when (item) {
            is ViewItem.Image -> {
                holder.apply {
                    /*
                    Glide.with(itemView)
                            .load(item.imageResource)
                            .into(imageView)
                    imageView.setOnClickListener {

                    }
                     */
                }
            }
            is ViewItem.Property -> {
                holder.apply {
                    /*
                    if (item.icon != null) {
                        iconImageView.setImageResource(item.icon)
                    }
                    else {
                        iconImageView.isVisible = false
                    }

                    textView.text = item.value


                    if (item.onClick != null) {
                        itemView.setOnClickListener {
                            item.onClick?.invoke()
                        }
                    }
                    else {
                        itemView.setOnClickListener(null)
                        itemView.setBackgroundColor(Color.TRANSPARENT)
                    }

                    item.onInit?.let { init ->
                        init.invoke(this)
                    }
                      */
                }
            }
        }
    }

    fun replaceItems(items: List<ViewItem>) {
        this.items = items
        notifyDataSetChanged()
    }

    inner class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer
}

I usually initialize the adapter with empty items.

adapter = LocalListAdapter()
recyclerView.adapter = adapter

When items are loaded from network or database, call replaceItems.

val items = mutableListOf<ViewItem>()

// load image 
items.add(ViewItem.Image("https://code.luasoftware.com/img/logo-badge.png"))

// load property - info
ViewItem.Property(R.drawable.ic_map_black_24dp, "My Address").also {
    items.add(it)
}

// load property - info + click
ViewItem.Property(R.drawable.ic_access_time_black_24dp, 
        "Click Me",
        onClick = { 
            // do something
        }).also {
    items.add(it)
}

adapter.replaceItems(items)
This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.