隨着業務的急劇擴張,一些架構上的調整也隨之破土動工。從最初的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數據集合。
不管是面向對象,仍是面向過程,亦或者函數式編程...自己都沒有特別明確的邊界。咱們要作的應該是讓優秀的思想爲咱們所用,提升咱們的生產力,最終實現「面向自由編程」~~~
最後,與君共勉!