在 Recyclerview Kotlin 中使用多布局绑定的正确方法

Posted

技术标签:

【中文标题】在 Recyclerview Kotlin 中使用多布局绑定的正确方法【英文标题】:Proper Way of using Multiple Layout Binding in Recyclerview Kotlin 【发布时间】:2021-09-02 14:08:24 【问题描述】:

嘿,我在 Recyclerview 中有多个布局。我想更改为视图绑定。我有多个布局,并且在所有布局中都有相同的 id,唯一的区别是位置不同。那么我怎样才能为此制作视图持有人。我想避免多个视图持有者。我试过不想用这个Multiple View Holder 有没有可能这样做?因为viewholder中的所有代码都是一样的。谢谢

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_
    android:layout_
    android:orientation="vertical">

    <ImageView
        android:id="@+id/imageView"
        android:layout_gravity="center"
        android:layout_
        android:layout_ />

</LinearLayout>

Horizo​​ntal.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_
    android:layout_
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/imageView"
        android:layout_gravity="center"
        android:layout_
        android:layout_ />

</LinearLayout>

【问题讨论】:

【参考方案1】:

使用一个ViewHolder 没有合适的方法来做到这一点,最好的解决方案是为每个不同的ViewHolder 创建多个 ViewHolder,因为这是 ViewHolder 模式的核心优势。

但是,如果您坚持使用一个 ViewHolder,我想出了一个解决方案,但最后,您必须分别处理每个布局绑定。

适配器

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)
        
    

但请注意,这不是推荐的解决方案,使用两个单独的视图持有者将允许您分别处理它们中的每一个。


更新

如果您需要使用多个 ViewHolder 来实现它,您必须为每个布局创建一个单独的 ViewHolder 并在其自己的 ViewHolder 中处理所有内部交互,如下所示

Horizo​​ntalViewHolder.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)
        
    

    ......

这将使您能够灵活地在单独的ViewHolder 中处理每个布局的交互。

【讨论】:

嘿@HamzaSharuf 感谢您的帮助。这样做的正确方法是什么?【参考方案2】:

我使用import androidx.recyclerview.widget.ListAdapter 而不是RecyclerView.Adapter,因为它比它有很多优点。

我从这里学到的,Recycler View Codelab

这是我使用 RecyclerView 的方式,建议您使用 2 个不同的 ViewHolders。

示例:


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

【讨论】:

以上是关于在 Recyclerview Kotlin 中使用多布局绑定的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 中的 RecyclerView itemClickListener [关闭]

Kotlin Part4, RecyclerView 使用Kotlin的适配器模型开发

如何在 Kotlin 中使用与 Activity 和 Fragment 相同的 RecyclerView 适配器?

在 Recyclerview Kotlin 中使用多布局绑定的正确方法

如何在 Kotlin 中将 listView 转换为 RecyclerView

Kotlin中实现RecyclerView嵌套RecyclerView