使用Databinding为Recyclerview使用同一个ViewHolder加载不同Item
提示:在阅读本篇文章前,你最好对android databinding有一定了解,本文使用的代码均为kotlin,但是不用担心,都很简单
最近在写项目的时候使用了databinding技术,突发奇想,databinding是不是也能应用于recyclerview中,让加载多个不同的item更简单呢。在网上搜索过后读到了<a href="https://medium.com/androiddevelopers/android-data-binding-recyclerview-db7c40d9f0e4?">这篇文章</a>,发现作者巧妙的使用了原有的函数来实现,在结合了自身使用实践之后,就有了现在这篇博客。
提问环节
为了让阅读过程更有目的性,先提几个问题,让大家带着问题来阅读
<strong>
adaper部分
Q1:onCreateViewHolder中怎么动态的创建不同类型的ViewHolder
Q2:onBindViewHolder中怎么绑定不同类型的对象到viewHolder里
viewholder部分
Q3:怎么把传过来的不同类型对象绑定到layout上面
</strong>
我们先来看看base adapter的代码
abstract class BaseAdapter : RecyclerView.Adapter<MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = DataBindingUtil.inflate<ViewDataBinding>(
layoutInflater,viewType,parent,false
)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val obj = getObjForPosition(position)
holder.bind(obj)
}
override fun getItemViewType(position: Int): Int {
return getLayoutIdForPosition(position)
}
abstract fun getObjForPosition(pos:Int):Any?
abstract fun getLayoutIdForPosition(pos:Int):Int
}
在<strong>onCreateViewHolder函数里</strong>我们首先初始化了一个layoutInflater,再使用DataBindingUtil创建了一个viewDatabinding对象,<strong>注意到这里的第二个参数很奇怪,居然是viewType</strong>,这是为什么呢,我们接下来会分析到。在函数的最后,我们创建了一个viewholder并返回,构造参数传入了我们创建的ViewDatabinding对象。
接下来,是<strong>onBindViewHolder函数</strong>,我们首先使用<strong>接口getObjForPosition(position)</strong>获取到了一个类型为any?的对象,在kotlin里面,any基本可以等同于java中的object,并把这个对象通过viewholder的bind方法与viewholder进行了绑定操作
<strong>重写了getItemViewType方法</strong>,在这个方法里,我们通过另一个<strong>接口getLayoutIdForPosition获取到了一个类型为Int的返回值</strong>,通过这个函数,我们可以看出,这种使用方法<strong>把layout id当作了itemviewtype,在一个Int对象中传递了两个信息</strong>,同时由于layout id不会重复,又保证了itemtype的唯一性。
<strong>总结1:
到了这里,我们可以回答前两个问题了
Q1:onCreateViewHolder中怎么动态的创建不同类型的ViewHolder
并没有创建不同类型的viewholder,只使用了一个MyViewHolder
Q2:onBindViewHolder中怎么绑定不同类型的对象到viewHolder里
通过抽象接口getLayoutIdForPosition将layout id作为返回值在getItemViewType中返回,再在onBindViewHolder中使用抽象接口getObjForPosition获得类型为Any?的对象并绑定到viewholder实现了将不同类型的对象绑定到viewHolder中
</strong>
我们来看一个具体的adapter例子
class MyAdapter(layouts:List<Int>)
: MultipleLayoutAdapter(layouts){
var myData:List<Moment>? = null
var header: UserInfo? = null
override fun getObjForPosition(position: Int): Any? {
return if (position == 0)
header
else
myData?.get(position - 1)
}
override fun getItemCount(): Int {
return if (myData== null)
1
else
myData?.size!! + 1
}
}
实现了上面提到的两个抽象接口,一个返回了对象的数量,一个返回了根据位置计算的layout id
<strong>viewholder部分</strong>
通过上述的过程,我们又会产生新的疑问,并没有使用多个viewholder来适配多个item,那是怎么使用同一个viewholder来搭配不同的layout的呢?内部又是怎么绑定的呢?
先来看一个viewHolder的代码
class MyViewHolder(private val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
private val context: Context = binding.root.context
fun bind(obj:Any?){
binding.setVariable(BR.obj,obj)
binding.executePendingBindings()
}
}
我们看到<strong>构造参数,是一个ViewDatabinding类型</strong>,在<strong>bind函数里,使用viewdatabinding对象的setVariable传入了参数</strong>obj,并且调用了<strong>executePendingBindings进行刷新</strong>,这样,我们的界面上就可以访问这个obj类了。
看一个layout的例子
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="obj"
type="com.u3coding.wemoment_03_12.data.entity.UserInfo" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="@dimen/header_view_height">
<ImageView
android:id="@+id/background_iv"
android:layout_width="match_parent"
android:layout_height="@dimen/header_background_image_height"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/name_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/header_avatar_margin_right"
android:layout_marginBottom="@dimen/header_avatar_margin_bottom"
android:text="@{obj.username}"
android:textColor="@color/header_name_color"
android:textSize="@dimen/name_size" />
<ImageView
android:id="@+id/avatar_iv"
android:layout_width="@dimen/header_avatar_size"
android:layout_height="54dp"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/avatar_magrin" />
</FrameLayout>
</layout>
<strong>Q&A
现在我们可以回答全部的问题了
Q1:onCreateViewHolder中怎么动态的创建不同类型的ViewHolder
并没有创建不同类型的viewholder,只使用了一个MyViewHolder
Q2:onBindViewHolder中怎么绑定不同类型的对象到viewHolder里
通过抽象接口getLayoutIdForPosition将layout id作为返回值在getItemViewType中返回,再在onBindViewHolder中使用抽象接口getObjForPosition获得类型为Any?的对象并绑定到viewholder实现了将不同类型的对象绑定到viewHolder中
Q3:怎么把传过来的不同类型对象绑定到layout上面
使用viewdatabinding的setVariable方法直接绑定到具体的变量上,再在布局中使用的时候指定类型,直接使用即可
此套方法巧妙的利用了databinding的特点,给adapter中itemtype赋予更多的含义,来实现了同一个viewholder对应不同布局的目的,十分的巧妙,尤其是给viewtype赋予更多含义,让人联想到了android view里面的measurespec,同样是给一个int赋予多重含义
</strong>