内容简介:随着业务的急剧扩张,一些架构上的调整也随之破土动工。从最初的MVC,管他是唱、跳、Rap,还是打篮球。通通写在Activity里;再到MVP阶段的业务与View分离;然后就是现在的MVVM。关于MVVM的内容,可以在我之前的文章中看到:
随着业务的急剧扩张,一些架构上的调整也随之破土动工。从最初的MVC,管他是唱、跳、Rap,还是打篮球。通通写在Activity里;再到MVP阶段的业务与View分离;然后就是现在的MVVM。
关于MVVM的内容,可以在我之前的文章中看到:
一点点入坑JetPack:实战前戏NetworkBoundResource篇
我猜可能有小伙伴们会不解,上文一顿瞎BB,和题目中的函数式编程、DiffUtil又有啥关系呢。不要着急,这一整个系列将 承接 上一个 MVVM系列 ,围绕 函数式编程 彻底展开一个从 业务层面 往 思想层面 理解的一个过程。
这篇系列融合了很多公司大佬们的架构分享,加上我自己思考总结的一篇文章。希望可以给各位小伙伴们带来收获,当然也欢迎大家各抒己见,闭门造车就太不real了。
正文
作为系列开篇第一章,我打算搞点实战意义比较强的。所以这篇文章不会上来就扯思想上的东西,而是主要以DiffUtil的用法为主。
主要包括以下部分:
- 1、notifyDataSetChanged()
- 2、DiffUtil基本用法
- 3、DiffUtil的思考
- 番外篇:源码分析
闲话就不多说了,咱们搞快点、搞快点!
一、notifyDataSetChanged()
我相信这个方法,咱们大家都不陌生吧。在那个“懵懂无知”的编程初期,不知道有多少小伙伴和我一样,靠着 notifyDataSetChanged()
,一招鲜吃遍天下。
直到后来发现,数据量多了之后, notifyDataSetChanged()
变得巨慢无比...此时的自己只能“不满”的喷一下Google:你tm就不能优化一下么?...
直到自己了解到了 notifyItemChanged()
、 notifyItemInserted()
等方法的时候。才知道就算自己“编程时长两年半”,该“蔡”还是“蔡”...
刚刚提到的那些方法,其实作为“职场老司机”,我猜大家应该很熟悉它们的用法。当然还有Payload机制下的局部 bindData()
。
关于传统 RecyclerView
的用法,就不多费口舌了,毕竟都是些基本操作。接下来就让咱们走进 DiffUtil
:
二、DiffUtil基本用法
从名字中,我们很容易猜到它的作用:一个帮我们diff数据集的工具。
对于之前的我们来说,diff的操作,都是我们业务方自己去处理的问题。然后根据数据的变动,自行选择使用什么样的notify方法。
而DiffUtil就是帮我们做这部分内容,然后根据我们的具体实现,自动帮我们去notify。直接裸上代码,毕竟能点进来的小伙伴,技术实力都不会太差。想要使用DiffUtil,第一步是继承特定的接口:
2.1、继承DiffUtil.Callback
先定一个数据结构:
// 不要在意这些变量是啥意思,就是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我们的数据集了。
2.2、调用
我们可以在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的思考
上述的问题,我猜很多小伙伴都遇到过。因为一些模式或者架构的原因。导致我们很多逻辑操作,都是使用同一个集合的引用,因此改变也是同一个集合元素。那么这种情况下对于 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
数据集合。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 思想交融,Android中的函数式编程(2):什么是函数式编程
- Python 拓展之特殊函数(lambda 函数,map 函数,filter 函数,reduce 函数)
- Python 函数调用&定义函数&函数参数
- python基础教程:函数,函数,函数,重要的事说三遍
- C++函数中那些不可以被声明为虚函数的函数
- 017.Python函数匿名函数
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
一只小鸟告诉我的事
[美]比兹·斯通 / 顾雨佳 / 中信出版社 / 2014-11 / 59.00元
比兹•斯通,无疑是自乔布斯后的又一个硅谷奇迹! 70后的他,出身贫苦,一无所有,却又特立独行,充满智慧。从他这本自传中,我们知道他和乔布斯一样,大学都没读完就辍学做了一名图书封面设计师,然后创建了赞架(Xanga)网站,又进了谷歌。在经济上打了翻身仗后,他毅然放弃了安逸的生活,从零开始,和朋友创建了世界最知名的社交平台推特(Twitter)。当推特奇迹般地改变着世界时,他又悄然离去,创建了自......一起来看看 《一只小鸟告诉我的事》 这本书的介绍吧!