在 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>
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_
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
中处理所有内部交互,如下所示
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
中处理每个布局的交互。
【讨论】:
嘿@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 中使用多布局绑定的正确方法