随着业务的急剧扩张,一些架构上的调整也随之破土动工。从最初的MVC,管他是唱、跳、Rap,仍是打篮球。统统写在Activity里;再到MVP阶段的业务与View分离;而后就是如今的MVVM。编程
关于MVVM的内容,能够在我以前的文章中看到:数据结构
一点点入坑JetPack:实战前戏NetworkBoundResource篇函数式编程
我猜可能有小伙伴们会不解,上文一顿瞎BB,和题目中的函数式编程、DiffUtil又有啥关系呢。不要着急,这一整个系列将承接上一个MVVM系列,围绕函数式编程完全展开一个从业务层面往思想层面理解的一个过程。工具
这篇系列融合了不少公司大佬们的架构分享,加上我本身思考总结的一篇文章。但愿能够给各位小伙伴们带来收获,固然也欢迎你们各抒己见,闭门造车就太不real了。源码分析
做为系列开篇第一章,我打算搞点实战意义比较强的。因此这篇文章不会上来就扯思想上的东西,而是主要以DiffUtil的用法为主。post
主要包括如下部分:
闲话就很少说了,我们搞快点、搞快点!
我相信这个方法,我们你们都不陌生吧。在那个“懵懂无知”的编程初期,不知道有多少小伙伴和我同样,靠着notifyDataSetChanged()
,一招鲜吃遍天下。
直到后来发现,数据量多了以后,notifyDataSetChanged()
变得巨慢无比...此时的本身只能“不满”的喷一下Google:你tm就不能优化一下么?...
直到本身了解到了notifyItemChanged()
、notifyItemInserted()
等方法的时候。才知道就算本身“编程时长两年半”,该“蔡”仍是“蔡”...
刚刚提到的那些方法,其实做为“职场老司机”,我猜你们应该很熟悉它们的用法。固然还有Payload机制下的局部bindData()
。
关于传统RecyclerView
的用法,就很少费口舌了,毕竟都是些基本操做。接下来就让我们走进DiffUtil
:
从名字中,咱们很容易猜到它的做用:一个帮咱们diff数据集的工具。
对于以前的咱们来讲,diff的操做,都是咱们业务方本身去处理的问题。而后根据数据的变更,自行选择使用什么样的notify方法。
而DiffUtil就是帮咱们作这部份内容,而后根据咱们的具体实现,自动帮咱们去notify。直接裸上代码,毕竟能点进来的小伙伴,技术实力都不会太差。想要使用DiffUtil,第一步是继承特定的接口:
先定一个数据结构:
// 不要在乎这些变量是啥意思,就是3个不一样的变量
data class Book(val id: Long, val name: String, val version: Long)
复制代码
而后就是DiffUtil.Callback
的实现类:
class BooksDiffCallback : DiffUtil.Callback() {
private var oldData = emptyList<Book>()
private var data = oldData
fun update(data: List<Book>) {
oldData = this.data
this.data = data
}
// 若是此方法返回true,说明来个数据集中同一个position位置的数据没有变化,至于如何notify须要参考areContentsTheSame()的返回值
// 若是此方法返回false,直接刷新item
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldData[oldItemPosition]
val newItem = data[newItemPosition]
return oldItem === newItem || oldItem.id == newItem.id
}
// 此方法会在areItemsTheSame()返回true的时候调用。
// 若是返回true,则意味着数据同样,Item也同样,不须要刷新。(这里属于业务方自行实现,好比个人实现就是当Book的version相同时,业务上认为数据相同不须要刷新)
// 若是返回false,则意味着数据不一样,须要刷新。不过这里还有一个分支,那就是是否Payload。此时便会走到getChangePayload()中
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldData[oldItemPosition]
val newItem = data[newItemPosition]
return oldItem.version == newItem.version
}
// 这里就是普通Payload的方法,当version不一样且name不一样时,咱们就告诉DiffUtil使用PAYLOAD_NAME做为Payload的表示
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
val list = mutableListOf<Any>()
val oldItem = oldData[oldItemPosition]
val newItem = data[newItemPosition]
if (oldItem.name != newItem.name) {
list.add(PAYLOAD_NAME)
return list
}
companion object {
val PAYLOAD_NAME = Any()
}
override fun getOldListSize(): Int {
return oldData.size
}
override fun getNewListSize(): Int {
return data.size
}
}
复制代码
完成这一步,咱们就能够set咱们的数据集了。
咱们能够在Adapter中简单的封装一个方法:
class BooksAdapter :RecyclerView.Adapter...{
private val diffCallback = BooksDiffCallback()
// ...省略部分代码
// BookViewHolder就是一个普通的ViewHolder
override fun onBindViewHolder(viewHolder: BookViewHolder, book: Book) {
viewHolder.bindData(book, viewHolder.adapterPosition)
}
override fun onBindViewHolder(holder: BookViewHolder, item: Book, payloads:MutableList<Any>) {
if (payloads.isNullOrEmpty()) {
onBindViewHolder(holder, item)
return
}
if(payloads.contains(PAYLOAD_NAME)){
// 调用BookViewHolder中业务方本身的局部刷新View的方法
viewHolder.bindData(book.name, viewHolder.adapterPosition)
}
}
// 对外暴露
fun updateData(items: List<Books>) {
this.items = items
diffCallback.update(items)
// 第二个参数false是啥意思呢?简单来讲到Adapter被调用了notifyItemMoved()时,不使用动画。
DiffUtil.calculateDiff(diffCallback, false).dispatchUpdatesTo(this)
}
}
复制代码
使用的时候,直接调用updateData()
,传入咱们的新数据集合,无需任何其余操做。
到这咱们的DiffUtil
用法就结束了。我相信已经开始用DiffUtil
的小伙伴必定会遇到下面这个问题:
数据集合都是同一个,由于都是直接操做同一个集合的引用,所以致使DiffUtil的时候各类不生效。
上述的问题,我猜不少小伙伴都遇到过。由于一些模式或者架构的缘由。致使咱们不少逻辑操做,都是使用同一个集合的引用,所以改变也是同一个集合元素。那么这种状况下对于DiffUtil
来讲,至始至终oldData和newData都是同一个集合,那就不存在diff这一说了。
若是你们能感觉到这其中的别扭之处,那么离理解,我想要聊的函数式编程就不远。
由于DiffUtil
设计自己就是对不一样的集合对象进行diff。所以咱们在update的时候,就必需要输入俩个不一样的集合实例。
而这偏偏知足了函数式编程(Functional Programming)所强调的俩点中的一点:不可变(immutable)。注意这个英文单词immutable,以及于之对立的mutable。不知道你们有没有留意到Kotlin中,大量的使用了这类单词。简单举几个例子: MutableList
、MutableMap
...等等
函数式编程强调的另外一点是:无状态(stateless)
你们再思考一个问题:RecycleView是啥?不就是一个UI控件么。咱们要作的是啥?不就是给RecycleView一个数据集,而后让它展现出来。
那么咱们简化一下RecycleView的这个模型,是否是RecycleView的这一系列操做就像一个函数/公式?给定一个输入,一定有一个输出。
嘚吧嘚,扯了这么多“玄之又玄”的东西,想表达啥意思呢。UI操做自己就像函数表达式同样,至于一切的数据变化,状态改变,那是输入给UI前的变换(transform)。
还记不记得我们在上一个系列聊MVVM的时候,提到了数据驱动。Google抽象出了ViewModel
就是让咱们去作数据的变换(transform)。变换完毕以后再输入给UI模块。对于我们的例子来讲,在Viewmodel之中变换完数据,把变化后的新数据集合,丢给DiffUtil,这才是正确的使用方式。
而此次整个系列所聊的内容就基本发生在ViewModel
这一层。咱们应该使用函数编程的思想去transform数据集合。
不管是面向对象,仍是面向过程,亦或者函数式编程...自己都没有特别明确的边界。咱们要作的应该是让优秀的思想为咱们所用,提升咱们的生产力,最终实现“面向自由编程”~~~
最后,与君共勉!