RecyclerView通配Adapter-Kotlin

1,603 阅读3分钟
天才第一步
天才第一步

先说一下整体思想: 如果我们想写一个通用的adapter,那么我们得让adapter里的方法变成大家都能用的(废话),所以onBindViewHolder我们拿到adapter之外实现,这样的话,adapter里有个性的方法就不复存在了

效果图
效果图

环境配置

环境配置好了,我们先来了解一下DiffUtil的工作原理。
第一次发布文章,准备的也不是那么充分,所以....尽情的点链接吧 DiffUtil详解

正餐

  • ViewHolder
inner class BaseViewHolder<out T : ViewDataBinding>(val binding: T) : RecyclerView.ViewHolder(binding.root) {
        init {
            binding.executePendingBindings()
        }
    }

因为我们使用了DataBinding,所以直接在ViewHolder里保存ViewDataBinding对象即可,省去了传统方法初始化item里所有变量的麻烦。 因为我们要写的是通用的adapter,所以泛型是必不可少的。

  • onCreateViewHolder
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): BaseViewHolder<T> {
        val view = LayoutInflater.from(context).inflate(resId, null)
        val binding = DataBindingUtil.bind<T>(view)
        return BaseViewHolder(binding)
    }

和传统写法没啥区别,不做赘述

  • onBindViewHolder
override fun onBindViewHolder(holder: BaseViewHolder<T>?, position: Int) {
        listener(holder!!, position)
    }

这里就是一个回调(实际上用的是lambda表达式),将holder与position作为参数传递给RecyclerView所在的activity或者fragment

到目前为止,最基本的东西都写完了,看下使用方法

//设置dataSize的同时,刷新adapter
var dataSize = 0
        set(value) {
            field = value
            notifyDataSetChanged()
        }
adapter = BaseAdapter<ItemBinding, Bean>(this, R.layout.item) {
            holder, position ->
            holder.binding.bean = realData[position]
        }
adapter.dataSize = data.size
就是这么简洁
就是这么简洁
  • 融入DiffUtil
//用于刷新adapter的方法,同时返回最新的数据
    fun refresh(oldData: List<K>, newData: List<K>, areContentsTheSame: (K, K) -> Boolean): List<K> {

        DiffUtil.calculateDiff(object : DiffUtil.Callback() {
            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                return oldData[oldItemPosition] == newData[newItemPosition]
            }

            override fun getOldListSize(): Int = oldData.size
            override fun getNewListSize(): Int = newData.size
            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                return areContentsTheSame(oldData[oldItemPosition], newData[newItemPosition])
            }
        }).dispatchUpdatesTo(this)

        //这里需要注意一下 我们要先将DiffUtil.Result绑定到adapter之后
        //再通知adapter数据长度变化
        dataSize = newData.size
        return newData
    }

需要注意的地方是,DiffUtil.DiffResult绑定adapter之后,会自己选择调用adapter的四种刷新方法,详细的可以自己看下源码。
所以我们的dataSize在这时,改成私有变量,去掉自定义setter
var dataSize = 0

然后这里实现的areContentsTheSame和onBindViewHolder差不多,都是属于比较有个性的方法(传进来的数据类不一样,比较的属性不一样),所以我们也用同样的方法, 拿出去实现。

到这里差不多该结束了,使用:

 adapter = BaseAdapter<ItemBinding, Bean>(this, R.layout.item) {
            holder, position ->
            holder.binding.bean = realData[position]
        }
realData = adapter.refresh(oldList, newList) { (name, age), (name1, age1) ->
            if (name != name1) false else age == age1
        }
End
End
  • 附件

直接贴上来两个类的代码吧 MainActivity.kt

class MainActivity: AppCompatActivity() {

    private var flag = true
    private var adapter: BaseAdapter<ItemBinding, Bean> by Delegates.notNull()
    private var realData: List<Bean> by Delegates.notNull()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityPracticeBinding = DataBindingUtil.setContentView(this, R.layout.activity_practice)

        val list: ArrayList<Bean> = ArrayList()
        val data: ArrayList<Bean> = ArrayList()

        //Ops, these are fake data
        val bean = Bean("name", "age")
        val bean2 = bean.copy(name = "2")
        val bean3 = bean.copy(name = "3")
        val bean4 = bean.copy(name = "4")
        val bean5 = bean.copy(name = "5")
        with(list) {
            add(bean);add(bean2);add(bean3);add(bean4);add(bean5);add(bean3)
        }
        with(data) {
            add(bean);add(bean2);add(bean3);add(bean5);add(bean3);add(bean2);add(bean3);add(bean)
        }

        adapter = BaseAdapter<ItemBinding, Bean>(this, R.layout.item) {
            holder, position ->
            holder.binding.bean = realData[position]
        }
        refresh(newList = list)
        with(binding) {
            recyclerView.adapter = adapter
            recyclerView.layoutManager = LinearLayoutManager(this@MainActivity)
            refresh.setOnClickListener {
                refresh(if (flag) list else data, if (!flag) list else data)
                flag = !flag
            }
        }
    }

    private fun refresh(oldList: ArrayList<Bean> = ArrayList(), newList: ArrayList<Bean>) {
        realData = adapter.refresh(oldList, newList) { (name, age), (name1, age1) ->
            if (name != name1) false else age == age1
        }
    }
}

BaseAdapter.kt

class BaseAdapter<in T : ViewDataBinding, K>(
        val context: Context,
        val resId: Int,
        val listener: (BaseAdapter<T, K>.BaseViewHolder<T>, Int) -> Unit) : RecyclerView.Adapter<BaseAdapter<T, K>.BaseViewHolder<T>>() {

    var dataSize = 0

    override fun onBindViewHolder(holder: BaseViewHolder<T>?, position: Int) {
        listener(holder!!, position)
    }

    override fun getItemCount(): Int = dataSize

    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): BaseViewHolder<T> {
        val view = LayoutInflater.from(context).inflate(resId, null)
        val binding = DataBindingUtil.bind<T>(view)
        return BaseViewHolder(binding)
    }

    inner class BaseViewHolder<out T : ViewDataBinding>(val binding: T) : RecyclerView.ViewHolder(binding.root) {
        init {
            binding.executePendingBindings()
        }
    }

    //用于刷新adapter的方法,同时返回最新的数据
    fun refresh(oldData: List<K>, newData: List<K>, areContentsTheSame: (K, K) -> Boolean): List<K> {

        DiffUtil.calculateDiff(object : DiffUtil.Callback() {
            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                return oldData[oldItemPosition] == newData[newItemPosition]
            }

            override fun getOldListSize(): Int = oldData.size
            override fun getNewListSize(): Int = newData.size
            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                return areContentsTheSame(oldData[oldItemPosition], newData[newItemPosition])
            }
        }).dispatchUpdatesTo(this)

        //这里需要注意一下 我们要先将DiffUtil.Result绑定到adapter之后
        //再通知adapter数据长度变化
        dataSize = newData.size
        return newData
    }
}