美文网首页DataBanding
DataBinding 打造 RecyclerView 万能适配

DataBinding 打造 RecyclerView 万能适配

作者: ZEKI安卓学弟 | 来源:发表于2019-11-29 11:32 被阅读0次

    前言

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

    相关文章

      网友评论

        本文标题:DataBinding 打造 RecyclerView 万能适配

        本文链接:https://www.haomeiwen.com/subject/rxwwwctx.html