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

Proper Way of using Multiple Layout Binding in Recyclerview Kotlin

嘿,我在 Recyclerview 中有多个布局。我想更改为视图绑定。我有多个布局,内部在所有布局中都有相同的 id,唯一的区别是位置不同。那么我怎样才能为此制作视图持有人。我想避免使用多个视图持有者。我试过不想使用这个 有没有可能这样做?因为所有代码在 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_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>

使用一个 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 中处理所有内部交互,如下所示

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)
        }
    }

    ......
}

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

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

从这里学来的,Recycler View Codelab

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

示例:


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
}