【问题标题】:How to use ViewBinding in a RecyclerView.Adapter?如何在 RecyclerView.Adapter 中使用 ViewBinding?
【发布时间】:2020-02-26 22:30:29
【问题描述】:

我可以在这个典型的RecyclerView.Adapter 初始化代码中使用 ViewBindings 替换 findViewById 吗?我无法在对象中设置binding val,因为每个单元格的 ViewHolders 都不同。

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

【问题讨论】:

  • “每个单元格的 ViewHolders 是不同的”——因此每个 ViewHolder 都通过其专用的绑定对象进行自己的绑定。将CardBinding 传递给CardViewHolder 构造函数,使其具有该绑定对象。 IOW,这与使用 RecyclerView 的数据绑定没有什么不同,而且这种组合已经使用了几年。
  • 我发现你也可以使用CardBinding.bind(holder.cardView)。不知道bind方法的性能是否更差?
  • 查看我关于视图绑定的博客我解释了关于适配器片段和活动中绑定的反模式和模式,它也深入到了内部????? Androidbites|Viewbinding
  • 是的,你可以用看看我的回答

标签: android android-viewbinding


【解决方案1】:

你需要做的就是将生成的绑定类对象传递给持有者类的构造函数。在下面的示例中,我有 RecyclerView 项目的 row_payment XML 文件,生成的类是 RowPaymentBinding 就像这样

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

另外,请确保您通过访问传递的绑定类对象将根视图传递给 Viewholder 的父类,例如 RecyclerView.ViewHolder(itemBinding.root)

【讨论】:

  • 非常干净。谢谢你。对绑定函数的改进是:fun bind(paymentBean: PaymentBean) = with(itemBinding) { tvPaymentInvoiceNumber.text = paymentBean.invoiceNumber tvPaymentAmount.text = paymentBean.totalAmount } ...以避免重复写入itemBinding
  • 或者为了避免重复 'itemBinding' 你可以使用 'itemBinding.apply { ... }'
  • @robsn 是的,我们可以这样做.. 但这不是这个答案的目的,我想让初学者更容易,这就是我不使用范围功能的原因。跨度>
  • 如果你使用数据绑定,那么itemBinding.tvPaymentInvoiceNumber.text = paymentBean.invoiceNumber等等的目的是什么?你可以做binding.executePendingBindings()
  • @Chisko 我在这个例子中没有使用 DataBinding,而且我不喜欢数据绑定。
【解决方案2】:

binding 附加到 ViewHolder 而不是 View

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

你传递绑定,绑定传递binding.rootRecyclerView.ViewHolder(binding.root)

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

然后访问任何地方:

holder.binding.title

【讨论】:

  • 你应该在onCreateViewHolder而不是onBindViewHolder中创建绑定。
  • (因为这个答案目前可以解决ViewHolder 仅在onCreateViewHolder 中查找引用的想法-> ViewHolder 构造函数,而是为每个onBindViewHolder 调用运行findViewById ,消除 ViewHolder 实际“持有视图”的能力 - 另一个答案是正确的:stackoverflow.com/a/60427658/2413303)
  • 你是完全正确的。这就是我的第二个选择。根据您的推理,不应使用第一个。谢谢!
  • 您可能需要编辑答案本身以反映这一点。
【解决方案3】:

我写了一个简单且可重复使用的:

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

}

【讨论】:

  • 这会导致充气机做一次吗?
  • @Moustafa EL-Saghier 不,这只是 ViewHolder 的一个小装饰。
  • 你知道充气器在同一视图支架的多个视图类型中一次工作的任何方式
  • @Moustafa EL-Saghier 我没明白你的意思,视图持有者应该只对应一种视图类型。
  • 如果你的意思是产品部分有不同的视图类型,没有。 Recycerview 会检查缓存池中的每个视图类型是否有一个viewholder。如果没有,onCreateViewHolder 将被调用。
【解决方案4】:

你可以像这样使用视图绑定:

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
}

}

这里是 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>

【讨论】:

    【解决方案5】:

    如果你对反射没意见,我有一个更简单的方法来做到这一点。 只需调用 ViewGroup.toBinding() 即可获得所需的绑定对象。 但由于我们讨论的是反射,请记住您必须修改您的 proguard-rule 以使其即使对 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
    }
    

    我把所有这些都放到了一个开源项目中,你也可以看看。 不仅适用于 Adapter 使用,还包括 Activity 和 Fragment。如果您有任何意见,请告诉我。谢谢。

    https://github.com/Jintin/BindingExtension

    【讨论】:

    • 简洁明了,是否可以将它与带有 CompositeItem 的适配器一起用于部分排序?
    • 你是指 ConcatAdapter 还是 SortedList?两者都应该工作,但我不确定你在说什么。
    • 我是古玩,在这种情况下可以使用反射吗?觉得不是
    【解决方案6】:

    只需将您的模型 calss 传递到 xml 并将这些数据设置为 xml 这段代码看起来很好,并添加一个方法,您可以将这些数据添加到绑定中,就像您不需要一样,不需要为此调整 id

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.setData(listData[position])
    
    }
    
    
    fun setData(model: ListData) {
            with(binding) {
                data = model
                executePendingBindings()
            }
        }
    

    【讨论】:

      【解决方案7】:

      你可以像这样使用数据绑定。

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

      【讨论】:

        【解决方案8】:

        我采用了 Alan W. did 并为其添加了泛型。

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

        实现非常简单,避免了适配器类的强制转换:

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

        【讨论】:

        • 我考虑过使用泛型,但因为多视图类型而放弃了它。泛型将您的适配器限制为仅一种视图类型。
        【解决方案9】:
        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)
        }
        

        【讨论】:

          猜你喜欢
          • 2019-08-19
          • 1970-01-01
          • 2021-06-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多