思想交融,Android中的函数式编程(1):DiffUtil体验

前言

随着业务的急剧扩张,一些架构上的调整也随之破土动工。从最初的MVC,管他是唱、跳、Rap,还是打篮球。通通写在Activity里;再到MVP阶段的业务与View分离;然后就是现在的MVVM。

关于MVVM的内容,可以在我之前的文章中看到:

一点点入坑JetPack:ViewModel篇

一点点入坑JetPack:Lifecycle篇

一点点入坑JetPack:LiveData篇

一点点入坑JetPack:实战前戏NetworkBoundResource篇

一点点入坑JetPack(终章):实战MVVM

我猜可能有小伙伴们会不解,上文一顿瞎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中,大量的使用了这类单词。简单举几个例子:
MutableListMutableMap…等等

函数式编程强调的另一点是:无状态(stateless)

大家再思考一个问题:RecycleView是啥?不就是一个UI控件么。我们要做的是啥?不就是给RecycleView一个数据集,然后让它展示出来。

那么我们简化一下RecycleView的这个模型,是不是RecycleView的这一系列操作就像一个函数/公式?给定一个输入,必定有一个输出。

嘚吧嘚,扯了这么多“玄之又玄”的东西,想表达啥意思呢。UI操作本身就像函数表达式一样,至于一切的数据变化,状态改变,那是输入给UI前的变换(transform)。

还记不记得咱们在上一个系列聊MVVM的时候,提到了数据驱动。Google抽象出了ViewModel就是让我们去做数据的变换(transform)。变换完毕之后再输入给UI模块。对于咱们的例子来说,在Viewmodel之中变换完数据,把变化后的新数据集合,丢给DiffUtil,这才是正确的使用方式。

而这次整个系列所聊的内容就基本发生在ViewModel这一层。我们应该使用函数编程的思想去transform数据集合。

尾声

无论是面向对象,还是面向过程,亦或者函数式编程…本身都没有特别明确的边界。我们要做的应该是让优秀的思想为我们所用,提高我们的生产力,最终实现“面向自由编程”~~~

最后,与君共勉!

我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,以及我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~

个人公众号:咸鱼正翻身

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 1. Fun Palaces的在线体验可以让人们在家参与到各种有趣的活动。 2. 这些活动涵盖了多个领域,包括科学、艺术、音乐、文化等等。 3. 通过在线平台,人们可以与其他参与者交流、分享自己的想法和经验。 4. 在线体验让参与者不受时间和空间的限制,可以在任何时候、任何地点参与活动。 5. Fun Palaces的在线体验可以让人们了解不同的文化和社区。 6. 通过体验,人们可以学习新的技能和知识,拓展自己的视野。 7. 在线体验可以让人们在家享受到与他人互动的乐趣,减轻孤独感。 8. 参与Fun Palaces的在线体验可以帮助人们建立社交网络,结识新的朋友。 9. 在线体验可以让人们更加灵活地组织自己的时间,选择自己感兴趣的活动参加。 10. 通过在线平台,人们可以与专业的艺术家、科学家等专业人士进行交流,了解他们的工作和生活。 11. 在线体验可以让人们更加方便地访问各种文化活动和资源,尤其是对于身体有残疾或行动不便的人群。 12. 参与Fun Palaces的在线体验可以让人们感受到社区的温暖和互助精神。 13. 在线体验可以促进人们的创造力和想象力,鼓励他们发挥自己的才能和创意。 14. Fun Palaces的在线体验可以让人们在家感受到各种文化和艺术活动的氛围和乐趣。 15. 在线体验可以让人们更加方便地参与各种文化和艺术活动,省去了长时间的等待和排队。 16. 参与Fun Palaces的在线体验可以让人们感受到身心的放松和愉悦,降低压力和焦虑。 17. 在线体验可以让人们更加自由地表达自己的想法和观点,获得他人的认可和支持。 18. Fun Palaces的在线体验可以让人们感受到文化和艺术对生活的积极影响和意义。 ### 回答2: 在Fun Palaces的在线体验,我深深感受到了创造力和社区团结的力量。我参与了不同的活动和项目,与其他志愿者和参与者一起合作,共同为社区带来快乐和启发。 首先,我想告诉大家的是Fun Palaces的在线平台非常易于使用。我只需要注册一个账户,就能够浏览和参与各种活动。界面简洁明了,活动分类清晰,使我能够轻松地找到我感兴趣的活动并参加其。 其次,Fun Palaces的在线体验为我提供了与其他志愿者和参与者互动的机会。通过活动的聊天室和论坛,我能够和其他人分享我的想法和经验。我们一起交流,激发灵感,共同创造一种充满活力和创意的环境。 另外,Fun Palaces的在线活动丰富多样,涵盖了艺术、科学、音乐、手工艺等各个领域。我能够参与绘画工作坊、音乐演奏、科学实验和手工制作等多种活动。这让我不仅能够学习新的技能和知识,还能够发挥自己的创造力和想象力。 此外,Fun Palaces还提供了一些线上展览和演讲,让我能够了解不同领域的专家和艺术家的见解和作品。这为我打开了一扇了解更广阔世界的窗口,让我能够深入了解各个领域的知识和文化。 最后,我要说的是,在Fun Palaces的在线体验,我感受到了社区团结的力量。尽管我们分布在不同的地方,却通过互联网和共同的兴趣点连接在一起。大家热情友好,分享着自己的热爱和激情,让这个平台充满了欢声笑语和团结友爱的氛围。 总的来说,我的Fun Palaces在线体验非常愉快和有益。我通过参与各种活动和项目,帮助了社区,也丰富了自己的知识和技能。我非常感激这个平台给我带来的机会和快乐,我期待着再次加入Fun Palaces的活动。 ### 回答3: 1. 在Fun Palaces的在线体验,我发现它是一个非常有趣和创新的平台,让我可以在家享受到丰富多样的文化活动。 2. 在Fun Palaces的网站上,我发现了许多有趣的艺术和科学项目,可以让我与其他参与者进行交流和讨论,增加了我的社交互动。 3. 通过参与Fun Palaces的在线活动,我不仅获得了新的知识和技能,还扩展了我的兴趣领域,发现了更多的文化和艺术资源。 4. 在Fun Palaces的在线体验,我发现它提供了多种多样的参与方式,无论是观看在线演出还是参与在线工作坊,都能满足我的需求。 5. 通过参与Fun Palaces的在线活动,我能够与世界各地的人们互动和交流,打破了地域限制,增加了我的国际视野。 6. 在Fun Palaces的在线体验,我发现它注重参与者的反馈和意见,不断改进和提高用户体验。 7. 在Fun Palaces的在线活动,我能够在家放松自己,享受文化和艺术的乐趣,无需费时费力地外出。 8. 通过参与Fun Palaces的在线活动,我能够发掘出自己的潜能,尝试新的技艺和创意,提升了我的个人能力和自信心。 9. 在Fun Palaces的在线体验,我发现它为不同年龄段的人们提供了适合的活动和内容,让每个人都能找到自己感兴趣的项目。 10. 通过参与Fun Palaces的在线活动,我能够学习到各种传统和现代的文化艺术形式,拓宽了我的文化视野。 11. 在Fun Palaces的在线体验,我发现它开设了丰富的讲座和工作坊,让我能够深入了解各种主题和话题。 12. 通过参与Fun Palaces的在线活动,我能够培养自己的艺术和表演能力,提高了我的创造力和想象力。 13. 在Fun Palaces的在线体验,我发现它充满了互动和创意,让我能够积极参与和贡献自己的想法。 14. 通过参与Fun Palaces的在线活动,我能够认识到文化和艺术对个人成长和社会发展的重要性。 15. 在Fun Palaces的在线体验,我发现它不仅关注传统艺术形式,还鼓励创新和跨界合作,展示了不同领域的精彩交融。 16. 通过参与Fun Palaces的在线活动,我能够结识到许多志同道合的人们,一起分享和探讨各种文化和艺术的话题。 17. 在Fun Palaces的在线体验,我发现它提供了多种语言和文化背景的内容,让我能够了解和欣赏不同国家和地区的文化。 18. 通过参与Fun Palaces的在线活动,我能够表达自己的创意和独特观点,与其他参与者进行交流和互动。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值