DataBanding

DataBinding 打造 RecyclerView 万能适配

2019-11-29  本文已影响0人  ZEKI安卓学弟

前言

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 呢?有两个原因

· 在 BaseRecyclerAdapteronBindViewHolder 中,虽然拿到了具体的 Binding 类型和 binding 对象,但并不能为 bindingBean 对应的 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( )

​ · 为 bindingperson 赋值 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)
   }
}
上一篇 下一篇

猜你喜欢

热点阅读