1

Hey I have multiple layout in Recyclerview. I want to change into view binding. I have multiple layout and inside that have same id in all layout, only difference is position is different. So How can i make view Holder for that. I want to avoid multiple view holder. I tried don't want to use this Multiple View Holder Is there any possibility to do that? Because all code are same in viewholder. Thanks

AdapterClass.kt

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView


class AdapterClass(private val horizontal: Boolean = false) : RecyclerView.Adapter<AdapterViewHolder>() {


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AdapterViewHolder {
        val inflatedView: View = if (horizontal) {
            LayoutInflater.from(parent.context).inflate(R.layout.horizontal_layout, parent, false)
        } else {
            LayoutInflater.from(parent.context).inflate(R.layout.vertical_layout, parent, false)
        }

        return AdapterViewHolder(inflatedView)
    }

    override fun onBindViewHolder(holder: AdapterViewHolder, position: Int) {
            holder.bingImage(position)
        }
    }
    .........
}

AdapterViewHolder.kt

import android.graphics.drawable.Drawable
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource

class AdapterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

    fun bingImage(position: Int) {
        with(itemView) {
            val color = if (isSelected) {
                itemView.context.resources.getColor(R.color.blue)
            } else {
                itemView.context.resources.getColor(R.color.green)
            }
            main_container.setBackgroundColor(color)
        }
    }
}

Vertical.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/imageView"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

Horizontal.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/imageView"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>
Kotlin Learner
  • 3,995
  • 6
  • 47
  • 127

2 Answers2

3

There is no proper way to do that using one ViewHolder, The best solution is to create multiple ViewHolders for each different ViewHolder, because this is the core benefit of the ViewHolder pattern.

However, If you insist to do it using one ViewHolder, I came up with a solution but at last, you will have to handle each layout bindings separately.

Adapter

class AdapterClass(private val horizontal: Boolean = false) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return if (horizontal) AdapterViewHolder.fromHorizontal(parent)
        else AdapterViewHolder.fromVertical(parent)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        (holder as AdapterViewHolder<*>).bingImage(position)
    }

    ......
}

ViewHolder

class AdapterViewHolder<T : ViewBinding> private constructor(val binding: T) : RecyclerView.ViewHolder(binding.root) {

    fun bingImage(position: Int) {
        with(binding.root) {
            val color = if (isSelected) {
                context.resources.getColor(R.color.black)
            } else {
                context.resources.getColor(R.color.green)
            }
            if (binding is HorizontalBinding)
                binding.mainContainer.setBackgroundColor(color)
            if (binding is VerticalBinding)
                binding.mainContainer.setBackgroundColor(color)
        }
    }

    companion object {
        fun fromVertical(parent: ViewGroup): AdapterViewHolder<VerticalBinding> {
            val layoutInflater = LayoutInflater.from(parent.context)
            val binding = VerticalBinding.inflate(layoutInflater, parent, false)
            return AdapterViewHolder(binding)
        }

        fun fromHorizontal(parent: ViewGroup): AdapterViewHolder<HorizontalBinding> {
            val layoutInflater = LayoutInflater.from(parent.context)
            val binding = HorizontalBinding.inflate(layoutInflater, parent, false)
            return AdapterViewHolder(binding)
        }
    }
}

But note that this is not a recommended solution and using two separate view holders will allow you to handle each of them separately.


Update

If you need to implement that using multiple ViewHolders you will have to create a separate ViewHolder for each layout and handle all internal interactions inside its own ViewHolderas follows

HorizontalViewHolder.kt

class HorizontalViewHolder private constructor(val binding: HorizontalBinding) : RecyclerView.ViewHolder(binding.root) {

    fun bind(position: Int) {
        with(binding.root) {
            val color = if (isSelected) {
                context.resources.getColor(R.color.black)
            } else {
                context.resources.getColor(R.color.green)
            }
            binding.mainContainer.setBackgroundColor(color)
        }
    }

    companion object {
        fun from(parent: ViewGroup): HorizontalViewHolder {
            val layoutInflater = LayoutInflater.from(parent.context)
            val binding = HorizontalBinding.inflate(layoutInflater, parent, false)
            return HorizontalViewHolder(binding)
        }
    }
}

VerticalViewHolder.kt

class VerticalViewHolder private constructor(val binding: VerticalBinding) : RecyclerView.ViewHolder(binding.root) {

    fun bind(position: Int) {
        with(binding.root) {
            val color = if (isSelected) {
                context.resources.getColor(R.color.black)
            } else {
                context.resources.getColor(R.color.green)
            }
            binding.mainContainer.setBackgroundColor(color)
        }
    }

    companion object {
        fun from(parent: ViewGroup): VerticalViewHolder {
            val layoutInflater = LayoutInflater.from(parent.context)
            val binding = VerticalBinding.inflate(layoutInflater, parent, false)
            return VerticalViewHolder(binding)
        }
    }
}

AdapterClass.kt

class AdapterClass(private val horizontal: Boolean = false) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return if (horizontal) HorizontalViewHolder.from(parent)
        else VerticalViewHolder.from(parent)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when(holder){
            is HorizontalViewHolder -> holder.bind(position)
            is VerticalViewHolder -> holder.bind(position)
        }
    }

    ......
}

This will give you the flexibility to handle the interaction of each layout in a separate ViewHolder.

Hamza Sharaf
  • 811
  • 9
  • 25
0

I use import androidx.recyclerview.widget.ListAdapter instead of RecyclerView.Adapter because it has many advantages over it.

I learnt it from here, Recycler View Codelab

This is how I use RecyclerView and ya using 2 different ViewHolders is recommended.

example:


private val ITEM_VIEW_TYPE_HORIZONTAL = 123
private val ITEM_VIEW_TYPE_VERTICAL = 456

/**
 * Custom Data Class for this adapter
 */
data class YourData(
    val id: Long,
    val horizontal: Boolean,
    val data: Int
)

class YourAdapter(val clickListener: DataClickListener) :
    ListAdapter<DataItem, RecyclerView.ViewHolder>(ListCheckDiffCallback()) {

    /**
     * This Function will help you out in choosing whether you want vertical or horizontal VIEW TYPE
     */
    override fun getItemViewType(position: Int): Int {
        return when (getItem(position).type) {
            true -> ITEM_VIEW_TYPE_HORIZONTAL
            false -> ITEM_VIEW_TYPE_VERTICAL
        }
    }

    /**
     * The View Type Selected above will help this function in choosing appropriate ViewHolder
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            ITEM_VIEW_TYPE_HORIZONTAL -> HorizontalViewHolder.from(parent)
            ITEM_VIEW_TYPE_VERTICAL -> VerticalViewHolder.from(parent)
            else -> throw ClassCastException("Unknown viewType $viewType")
        }
    }

    /**
     * The View Holder Created above are used here.
     */
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is HorizontalViewHolder -> {
                val item = getItem(position) as DataItem.HorizontalClass
                holder.bind(item.yourData, clickListener)
            }
            is VerticalViewHolder -> {
                val item = getItem(position) as DataItem.VerticalClass
                holder.bind(item.yourData, clickListener)
            }
        }
    }

    /**
     * Vertical View Holder Class
     */
    class VerticalViewHolder private constructor(val binding: <REPLACE_WITH_BINDING_OBJECT>) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(item: YourData, clickListener: DataClickListener) {
            /**
             * change all your view data here
             * assign click listeners here
             *
             *  example -->
             *
             *  binding.xyz.setOnClickListener {
             *     clickListener.onClick(item)
             *  }
             */

        }

        companion object {
            fun from(parent: ViewGroup): VerticalViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)
                val view =  <REPLACE_WITH_BINDING_OBJECT>.inflate(R.layout.header, parent, false)
                return VerticalViewHolder(binding)
            }
        }
    }

    /**
     * Horizontal View Holder
     */
    class HorizontalViewHolder private constructor(val binding: <REPLACE_WITH_BINDING_OBJECT>) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(item: YourData, clickListener: DataClickListener) {
            /**
             * change all your view data here
             * assign click listeners here
             *
             *  example -->
             *
             *  binding.xyz.setOnClickListener {
             *     clickListener.onClick(item)
             *  }
             */
        }

        companion object {
            fun from(parent: ViewGroup): HorizontalViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)

                val binding =  <REPLACE_WITH_BINDING_OBJECT>.inflate(layoutInflater, parent, false)
                return HorizontalViewHolder(binding)
            }
        }
    }
}

/**
 * This function checks the difference between 2 different Lists.
 * 1. Old List
 * 2. New List
 */
class ListCheckDiffCallback : DiffUtil.ItemCallback<DataItem>() {
    override fun areItemsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
        return oldItem == newItem
    }
}

/**
 * Interface that can be called as per your wish.
 * I usually assign it inside the Fragment/Activity from where I am using the above Adapter.
 * like 
 *class MyFragment : Fragment(), DataClickListener
 */
interface DataClickListener {
    fun onClick(data: YourData)
}

/**
 * Your DataItem Class
 */
sealed class DataItem {
    data class HorizontalClass(val yourData: YourData) : DataItem() {
        override val id = yourData.id
        override val type = true
    }

    data class VerticalClass(val yourData: YourData) : DataItem() {
        override val id = yourData.id
        override val type = false
    }

    abstract val id: Long
    abstract val type: Boolean
}
lets start coding
  • 1,839
  • 1
  • 10
  • 19