前言
RecyclerView再Android开发中的使用场景是非常多的,然而再面对几乎一尘不变的列表显示时,我们需要不停地造轮子,写着几乎相似的代码,大大减少开发效率。
最原始的做法:写一个Adapter继承自RecyclerView.Adapter,复写 getItemCount ( ),在 OnBindViewHolder( ) 中进行数据与Item的绑定。省力一点的做法就是再提出一个abstract BaseAdapter,只需要每次继承BaseAdapter,复写onBindViewHolder就可以了。
但我觉得仍然不够,Google为我们提供了DataBinding,直接将复写onBindViewHolder这一步也省掉。
准备工作
在 Module 的 build.gradle 中开启dataBinding
android {
dataBinding {
enabled = true
}
}
引入RecyclerView
(我这里使用AndroidX ,可以直接在任意xml中design搜索RecyclerView并下载让工程自己引入)
implementation 'androidx.recyclerview:recyclerview:1.0.0'
在开始之前,你最好先了解DataBInding的基本用法(https://developer.android.com/topic/libraries/data-binding)
构造
abstract class BaseRecyclerAdapter<Bean, Binding : ViewDataBinding>
首先这个 BaseRecyclerAdapter 是抽象的(功能不同应该有不同的Adapter)
两个泛型参数 :
· Bean :整个列表的数据类
· Binding :这个 Item 的 Xml 的 Binding 类 ,它是继承自 ViewDataBinding 的
参数
constructor(
private val layoutRes: Int,
private val onCellClick: (Int) -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>()
· layoutRes :XML 文件的 id
· onCellClick :点击事件的闭包(其实就是 Java 的 Function ),也就是 BaseRecyclerAdapter 允许你传入一个 入参为 Int 返回为空的方法
List
在类中加入一个抽象属性 list 让子类实现(也可以直接放在构造函数中,看自己喜好)
abstract val baseList: MutableList<Bean>
构造BaseSimpleViewHolder
class BaseSimpleViewHolder<Binding : ViewDataBinding>(
itemView: View
) : RecyclerView.ViewHolder(itemView) {
val binding: Binding? by lazy {
DataBindingUtil.bind<Binding>(itemView)
}
}
了解 RecyclerView 的朋友都知道 Holder 是很重要的一个参数,每个Item在 OnBindViewHolder 的时候都会去用它的Holder;
按照以前的写法,我们是需要在这个Holder中定义例如 TextView、ImageView之类的控件引用的;
现在我们有了DataBinding,只需要在Holder中拿到 Binding 的引用,整个 XML就被我们的 holder 拿到了,它的类型就是我们在定义 Class 的时候传入的 Binding;
采用 by lazy ,使其在第一次调用时才被赋值 (https://www.kotlincn.net/docs/reference/delegated-properties.html);
这里只是优化,直接赋值也是可以的
重写onCreateViewHolder
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RecyclerView.ViewHolder {
return BaseSimpleViewHolder<Binding>(
LayoutInflater.from(parent.context).inflate(layoutRes, parent, false)
)
}
就是返回了一个我们自己定义的 BaseSimpleViewHolder
重写getItemCount
override fun getItemCount() = baseList.size
重写onBindViewHolder
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder as BaseSimpleViewHolder<Binding>
holder.binding!!.root.setOnClickListener {
onCellClick(position)
}
bindData(holder.binding!!, position)
}
首先我们需要 将 holder 强转为我们的 BaseViewHolder ,并且将 BInding 类型传入;
为 binding 的根 View 添加一个点击事件,这里就是去调用我们在类的构造中传入的 闭包 ;
之后还需要一个抽象的 bindData( )方法 留给子类实现
abstract fun bindData(binding: Binding, position: Int)
既然我们已经有 BInding ,为什么还需要它的子类实现 bIndData 呢?有两个原因
· 在 BaseRecyclerAdapter 的 onBindViewHolder 中,虽然拿到了具体的 Binding 类型和 binding 对象,但并不能为 binding 中 Bean 对应的 object 赋值 ,而这个 object 只有在具体子类使用时才能得到
· 业务逻辑较重的代码是不建议放在 XML 中完成,DataBinding 能够让我们在 XMl 中完成大多数的数据绑定操作,但我们仍然需要一个 bindData 方法完成重逻辑处理;如果只是一个轻逻辑的 list 是不需要再额外写代码的
到此我们的 BaseRecyclerAdapter 是已经完成了
但我希望它还能提供一些经常的列表操作功能
额外功能
//替换整个列表
fun replaceData(newList: MutableList<Bean>) {
baseList.run {
clear()
addAll(newList)
}
notifyDataSetChanged()
}
//添加一个数据
fun addData(data: Bean) {
baseList.add(data)
notifyItemInserted(baseList.size - 1)
}
//删除指定位置数据
fun removeData(position: Int) {
baseList.removeAt(position)
notifyItemRemoved(position)
notifyItemRangeChanged(position, baseList.size)
}
//交换两个数据位置
fun onItemMove(fromPosition: Int, toPosition: Int) {
if (toPosition >= 0 && toPosition < baseList.size) {
Collections.swap(baseList, fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
}
}
}
完整的 Adapter 代码
abstract class BaseRecyclerAdapter<Bean, Binding : ViewDataBinding>
constructor(
private val layoutRes: Int,
private val onCellClick: (Int) -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
abstract val baseList: MutableList<Bean>
class BaseSimpleViewHolder<Binding : ViewDataBinding>(
itemView: View
) : RecyclerView.ViewHolder(itemView) {
val binding: Binding? by lazy {
DataBindingUtil.bind<Binding>(itemView)
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RecyclerView.ViewHolder {
return BaseSimpleViewHolder<Binding>(
LayoutInflater.from(parent.context).inflate(layoutRes, parent, false)
)
}
override fun getItemCount() = baseList.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder as BaseSimpleViewHolder<Binding>
holder.binding!!.root.setOnClickListener {
onCellClick(position)
}
bindData(holder.binding!!, position)
}
abstract fun bindData(binding: Binding, position: Int)
fun replaceData(newList: MutableList<Bean>) {
baseList.run {
clear()
addAll(newList)
}
notifyDataSetChanged()
}
fun addData(data: Bean) {
baseList.add(data)
notifyItemInserted(baseList.size - 1)
}
fun removeData(position: Int) {
baseList.removeAt(position)
notifyItemRemoved(position)
notifyItemRangeChanged(position, baseList.size)
}
fun onItemMove(fromPosition: Int, toPosition: Int) {
//交换位置
if (toPosition >= 0 && toPosition < baseList.size) {
Collections.swap(baseList, fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
}
}
}
使用(以好友列表为例)
Person数据类:
data class Person(
val uid: String,
val avatar: String,
val name: String
)
XMl文件:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data class="FriendListCellBinding">
<variable
name="person"
type="com.example.rubbishcommunity.model.Person" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cell"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="vertical">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/friend_portrait"
imageUrl="@{person.avatar}"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/friend_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="80dp"
android:text="@{person.name,default=默认名字}"
android:textColor="@color/black"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@+id/friend_portrait"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/friend_portrait"
app:layout_constraintTop_toTopOf="@+id/friend_portrait" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
指定 class 名为 FriendListCellBinding
添加变量 person
· 在 CircleImageView 中使用了适配器 imgUrl 属性,顺带贴出来吧(需要引入 Glide 4.0+ )
@BindingAdapter("imageUrl")
fun loadImage(imageView:ImageView, url:String?){
Glide.with(imageView.context).load(url)
.centerCrop()
.into(imageView)
}
· 在 TextView 中绑定数据 android:text="@{person.name,default=默认名字}"
朋友列表适配器
class FriendListAdapter(
val list: MutableList<Person>,
onCellClick: (Int) -> Unit
) : BaseRecyclerAdapter<Person, FriendListCellBinding>(
R.layout.cell_friend,
onCellClick
) {
override val baseList: MutableList<Person> = list
override fun bindData(binding: FriendListCellBinding, position: Int) {
binding.person = list[position]
}
}
· 在构造参数中接受一个 Person 泛型的 可变list 和一个点击事件的闭包
· 在继承 BaseRecyclerAdapter 时
· 传入泛型 数据类 **Person** 以及 XML 中指定的 Binding 类名 **FriendListCellBinding**
· 传入朋友列表 Item 的 XML 的 id 和 在构造函数中的点击事件的闭包
· 重写属性 baseList 直接赋值为我们在构造函数中接受的 list
· 重写 bindData( )
· 为 binding 的 person 赋值 binding.person = list[position]
由于这里只有显示一个名字和头像,没有重逻辑,所以绑定数据后就没有其他操作了
在Fragment中使用
findViewById<RecyclerView>(R.id.friendlsit).run {
val friendList = mutableListOf(
Person(
"aaaaa",
"https://ss1.baidu.com/-4o3dSag_xI4khGko9WTAnF6hhy/image/h%3D300/sign=a9e671b9a551f3dedcb2bf64a4eff0ec/4610b912c8fcc3cef70d70409845d688d53f20f7.jpg",
"测试A"
)
)
layoutManager = LinearLayoutManager(context)
adapter = FriendListAdapter(friendList) { position ->
jumpToChat(context, friendList[position].uid)
}
}
网友评论