如何在回收视图适配器中使用 ViewBinding?

在这个典型的 RecyclerView.Adapter初始化代码中,我是否可以使用 ViewBindings 来替换 findViewById?我不能在对象中设置 bindingval,因为每个单元格的 ViewHolder 是不同的。

class CardListAdapter(private val cards: LiveData<List<Card>>) : RecyclerView.Adapter<CardListAdapter.CardViewHolder>() {


class CardViewHolder(val cardView: View) : RecyclerView.ViewHolder(cardView)


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val binding = CardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return CardViewHolder(binding.root)
}


override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
val title = holder.cardView.findViewById<TextView>(R.id.title)
val description = holder.cardView.findViewById<TextView>(R.id.description)
val value = holder.cardView.findViewById<TextView>(R.id.value)
// ...
}
55459 次浏览

Attach the binding to the ViewHolder instead of the View

class CardViewHolder(val binding: CardBinding) : RecyclerView.ViewHolder(binding.root)

You pass the binding, the binding passes binding.root to RecyclerView.ViewHolder(binding.root)

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val binding = CardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return CardViewHolder(binding)
}

Then access anywhere with:

holder.binding.title

just pass your model calss into xml and set these data into xml this code look fine and add a method where you add these data into binding like you don,t need to fine the id for this

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.setData(listData[position])


}




fun setData(model: ListData) {
with(binding) {
data = model
executePendingBindings()
}
}

You may use data binding like this.

class CardListAdapter(
private val mActivity: FragmentActivity?
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mCustomLayoutBinding: CustomLayoutBinding? = null


inner class MyViewHolder(val mBinding: CustomLayoutBinding) :
RecyclerView.ViewHolder(mBinding.getRoot())
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (layoutInflater == null)
layoutInflater = LayoutInflater.from(parent.context)


var viewHolder: RecyclerView.ViewHolder? = null
val inflater = LayoutInflater.from(parent.context)


viewHolder = getViewHolder(parent, inflater)
return viewHolder!!
}
private fun getViewHolder(
parent: ViewGroup,
inflater: LayoutInflater
): RecyclerView.ViewHolder {
mCustomLayoutBinding =
DataBindingUtil.inflate(inflater, R.layout.custom_layout, parent, false)
return MyViewHolder(this!!.mAssistanceLogCustomLayoutBinding!!)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val taskModal = mArrayList.get(position)
holder.mBinding.txtTitle.setText(taskModal.title)
}
override fun getItemCount(): Int {
return assistanceArrayList.size
}


override fun getItemId(position: Int): Long {
return position.toLong()
}


override fun getItemViewType(position: Int): Int {
return position
}
}

What you need to do is pass the generated binding class object to the holder class constructor. In below example, I have row_payment XML file for RecyclerView item and the generated class is RowPaymentBinding so like this

    class PaymentAdapter(private val paymentList: List<PaymentBean>) : RecyclerView.Adapter<PaymentAdapter.PaymentHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PaymentHolder {
val itemBinding = RowPaymentBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return PaymentHolder(itemBinding)
}
    

override fun onBindViewHolder(holder: PaymentHolder, position: Int) {
val paymentBean: PaymentBean = paymentList[position]
holder.bind(paymentBean)
}
    

override fun getItemCount(): Int = paymentList.size
    

class PaymentHolder(private val itemBinding: RowPaymentBinding) : RecyclerView.ViewHolder(itemBinding.root) {
fun bind(paymentBean: PaymentBean) {
itemBinding.tvPaymentInvoiceNumber.text = paymentBean.invoiceNumber
itemBinding.tvPaymentAmount.text = paymentBean.totalAmount
}
}
}

Also, make sure you pass the root view to the parent class of Viewholder like this RecyclerView.ViewHolder(itemBinding.root) by accessing the passed binding class object.

You may use view-binding like this :

package com.example.kotlinprogramming.adapter


import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.kotlinprogramming.data.HobbiesData
import com.example.kotlinprogramming.databinding.ItemHobbieBinding


class HobbiesAdapter(var context: Context, var hobbiesList: List<HobbiesData>) :
RecyclerView.Adapter<HobbiesAdapter.HobbiesViewHolder>() {




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


override fun onBindViewHolder(holder: HobbiesViewHolder, position: Int) {
val hobbie = hobbiesList.get(position)
holder.viewBinding.tvHobbie.text = hobbie.title
}


inner class HobbiesViewHolder(var viewBinding: ItemHobbieBinding) : RecyclerView.ViewHolder(viewBinding.root) {
}


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


}

Here is item_hobbies.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_margin="12dp"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/tvHobbie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:gravity="center"
android:textSize="30sp"
tools:text="Hobbie1"
/>
</androidx.cardview.widget.CardView>

I wrote a simple and reusable one:

class ViewBindingVH constructor(val binding: ViewBinding) :
RecyclerView.ViewHolder(binding.root) {
    

companion object {
inline fun create(
parent: ViewGroup,
crossinline block: (inflater: LayoutInflater, container: ViewGroup, attach: Boolean) -> ViewBinding
) = ViewBindingVH(block(LayoutInflater.from(parent.context), parent, false))
}
}




class CardAdapter : RecyclerView.Adapter<ViewBindingVH>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewBindingVH {
return ViewBindingVH.create(parent, CardBinding::inflate)
}


override fun onBindViewHolder(holder: ViewBindingVH, position: Int) {
(holder.binding as CardBinding).apply {
//bind model to view
title.text = "some text"
descripiton.text = "some text"
}
}


}

If you're ok with reflection, I have a much easier way to do this. Just call ViewGroup.toBinding() then you can get the binding object you want. But since we're talk about reflection, remember you have to modify your proguard-rule to make it work even for proguard.

// inside adapter
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return MainViewHolder(parent.toBinding())
}


// ViewHolder
class MainViewHolder(private val binding: AdapterMainBinding) :
RecyclerView.ViewHolder(binding.root) {


fun bind(data: String) {
binding.name.text = data
}
}


// The magic reflection can reused everywhere.
inline fun <reified V : ViewBinding> ViewGroup.toBinding(): V {
return V::class.java.getMethod(
"inflate",
LayoutInflater::class.java,
ViewGroup::class.java,
Boolean::class.java
).invoke(null, LayoutInflater.from(context), this, false) as V
}

I put all this into an open source project you can take a look as well. Not only for Adapter usage but also include Activity and Fragment. And do let me know if you have any comment. Thanks.

https://github.com/Jintin/BindingExtension

I took what Alan W. did and added Generics to it.

class ViewBindingVH <VB: ViewBinding> constructor(val binding: VB) :
RecyclerView.ViewHolder(binding.root) {


companion object {
inline fun <VB: ViewBinding> create(
parent: ViewGroup,
crossinline block: (inflater: LayoutInflater, container: ViewGroup, attach: Boolean) -> VB
) = ViewBindingVH<VB>(block(LayoutInflater.from(parent.context), parent, false))
}}

The implementation is very easy and you avoid the casting on the adapter class:

class PlayerViewHolder : ListAdapter<Rate, ViewBindingVH<ChartItemBinding>>(RateDiff) {


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewBindingVH<ChartItemBinding> =
ViewBindingVH.create(parent, ChartItemBinding::inflate)




override fun onBindViewHolder(holder: ViewBindingVH<ChartItemBinding>, position: Int) {
val item = currentList[position]
holder.binding.apply {
        

}
}}


object RateDiff: DiffUtil.ItemCallback<Rate>(){
override fun areContentsTheSame(oldItem: Rate, newItem: Rate): Boolean {
return oldItem == newItem
}
override fun areItemsTheSame(oldItem: Rate, newItem: Rate): Boolean {
return oldItem == newItem
}}
abstract class BaseRecyclerViewAdapter<T : Any, VB : ViewBinding>(
private var dataList: ArrayList<T>)
: RecyclerView.Adapter<BaseRecyclerViewAdapter.MyViewViewHolder<VB>>()
{
protected var bindingInterface: GenericSimpleRecyclerBindingInterface<T, VB>? = null


class MyViewViewHolder<VB : ViewBinding>(val viewBinding: VB) :
RecyclerView.ViewHolder(viewBinding.root) {
fun <T : Any> bind(
item: T,
position: Int,
bindingInterface: GenericSimpleRecyclerBindingInterface<T, VB>
) = bindingInterface.bindData(item, position, viewBinding)
}


@SuppressLint("NotifyDataSetChanged")
fun updateList(list: ArrayList<T>) {
dataList.clear()
dataList.addAll(list)
notifyDataSetChanged()
}


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):MyViewViewHolder<VB> {
val view = inflateView(LayoutInflater.from(parent.context))
return MyViewViewHolder(view)
}


override fun onBindViewHolder(holder: MyViewViewHolder<VB>, position: Int) {
val item = dataList[position]
holder.bind(item, position, bindingInterface!!)
}


override fun getItemCount(): Int = dataList.size


abstract fun inflateView(inflater: LayoutInflater): VB
}


interface GenericSimpleRecyclerBindingInterface<T : Any, VB : ViewBinding> {
fun bindData(item: T, position: Int, viewBinding: VB)
}