DiffUtils 遇到 Kotlin,榨干视图局部刷新的最后一滴性能
Posted 冬天的毛毛雨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DiffUtils 遇到 Kotlin,榨干视图局部刷新的最后一滴性能相关的知识,希望对你有一定的参考价值。
前言:
RecyclerView 作为android 开发中最常用的开发组件,简单的静态页面,是不需要使用DiffUtils 的。为了提高RecyclerView的渲染性能,最容易想到的就是使用DiffUtils组件,一方面做到了只刷新某个变化了Item;另一方面通过DiffUtils 派发能够触发 RecyclerView默认动画效果,让界面更加优雅。
在前端各种双向绑定,数据驱动大行其道的今天,许多开发理念对Android同样适用;Kotlin 作为主要开发语言后,其各种语言特性,比如不可变数据,协程,Flow 与 Channel 让数据流组织和使用起来都更加清晰方便。
DiffUtils 的简单使用
DiffUtils 的使用起来也很简单,只需要简单的传入一个DiffCallback,重写其中的几个方法,DiffUtils 就能对比出新旧数据集差异,根据差异内容自动触发Adapter 的 增删改 通知,这也是我们在App 中最常用的使用方法。
在下面的示例中都使用Car类型作为数据类。
data class Car(val band: String, val color: Int, val image: String, val price: Int)
把Callback继续封装下,基本两行代码就可以实现adapter增删改的派发逻辑
val diffResult = DiffUtil.calculateDiff(SimpleDiffCallback(oldList, newList))
oldList.clear()
oldList.addAll(data)
diffResult.dispatchUpdatesTo(adapter)
//重写一个Callback 实现
class SimpleDiffCallback(
private val oldList: List<RenderData>,
private val newList: List<RenderData>
) : DiffUtil.Callback()
override fun areItemsTheSame(lh: Int, rh: Int) = from[lh].band == to[rh].band
override fun getOldListSize(): Int = oldList.size
override fun getNewListSize(): Int = newList.size
override fun areContentsTheSame(lh: Int, rh: Int) = from[lh] == to[rh]
高阶使用,使用DiffUtils 的 payload
上一节的使用方式满足一般的使用场景已经足够了,但是在一个Item Change 时仍然会刷新整个Item,数据仍然需要重新绑定一遍视图,为了解决这个问题,我们一般需要重写 DiffUtil.Callback 的 getChangePayload 方法。
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any?
通常的使用方式
private class SimpleDiffCallback(
private val oldList: List<Car>,
private val newList: List<Car>
) : DiffUtil.Callback()
//.....
//重写 getChangePayload 方法
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any?
val lh = oldList[oldItemPosition]
val rh = newList[newItemPosition]
//手动记录改变的数据
val payloads = mutableListOf<CarChange>()
if (lh.image != rh.image)
payloads.add(CarChange.ImageChange(rh.image))
if (lh.price != rh.price)
payloads.add(CarChange.PriceChange(rh.price))
if (lh.color != rh.color)
payloads.add(CarChange.ColorChange(rh.color))
return CarPayLoad(payloads)
data class CarPayLoad(val changes: List<CarChange>)
sealed class CarChange
data class ImageChange(val image: String): CarChange()
data class PriceChange(val price: Int): CarChange()
data class ColorChange(val color: Int): CarChange()
这样子在 Adapter 的 onBindViewHolder 中,我们就可以方便取到数据中具体某一项的变化,并针对性的更新到视图中。
override fun onBindViewHolder(holder: CarViewHolder,position: Int,payloads: MutableList<Any?>)
if (payloads.isNotEmpty())
for (payload in payloads)
if (payload is CarPayLoad)
for(change in payload.changes)
// 更新视图数据
when (change)
is CarChange.ColorChange -> holder.colorIv.setColor(change.color)
is CarChange.ImageChange -> holder.imageIv.setImaggSrc(change.image)
is CarChange.PriceChange -> holder.priceTv.setText(change.price)
else
super.onBindViewHolder(holder, position, payloads)
使用起来繁琐单并不困难,主要是模板代码比较多,怎么通过简单的封装,让业务逻辑更加专注与视图更新呢。
DiffUtils 遇到Kotlin,更优雅的View局部刷新方案
本文中使用的都是Kotlin的数据类,当然简单改动后应用到Java上也是可以的。
先上使用方式
- 业务专注于数据和视图的映射关系,定义数据视图映射,无需定义对象来记录变动的字段。
val binders: DiffUpdater.Binder<Car>.() -> Unit =
Car::color onChange (vh,color) ->
vh.colorIv.setColor(color)
Car::image onChange (vh,image) ->
vh.imageIv.setImageSrc(image)
Car::price onChange (vh,price) ->
vh.priceTv.setText(price.toString())
- 在Diffcallback 中使用DiffUpdater生成字段变动对象。
private class SimpleDiffCallback(
private val oldList: List<Car>,
private val newList: List<Car>
) : DiffUtil.Callback()
//.....
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any?
//记录变动
return DiffUpdater.createPayload(oldList[oldItemPosition],newList[newItemPosition])
- 在 onBindViewHolder,使用 DiffUpdater.Payload#dispatch 方法触发视图更新。
override fun onBindViewHolder(holder: CarViewHolder,position: Int,payloads: MutableList<Any?>)
if (payloads.isNotEmpty())
for(payload in payloads)
if(payload is DiffUpdater.Payload<*>)
//触发视图更新
(payload as DiffUpdater.Payload<Car>).dispatch(holder,binders)
else
super.onBindViewHolder(holder, position, payloads)
在使用上可以更加专注与数据和视图的绑定逻辑。
#附录
因为使用了Kotlin反射,记得加上相关依赖。
dependencies
implementation "org.jetbrains.kotlin:kotlin-reflect:$version"
最后附上完整的DiffUpater。
@Suppress("UNCHECKED_CAST")
object DiffUpdater
data class Payload<T>(
val newState: T,
val changed: List<KProperty1<T, *>>,
)
class Binder<T, VH : RecyclerView.ViewHolder>
val handlers = mutableMapOf<KProperty1<T, *>, (VH, Any?) -> Unit>()
infix fun <R> KProperty1<T, R>.onChange(action: (R) -> Unit)
handlers[this] = action as (VH, Any?) -> Unit
fun <T, VH : RecyclerView.ViewHolder> Payload<T>.dispatch(vh: VH, block: Binder<T,VH>.() -> Unit)
val binder = Binder<T,VH>()
block(binder)
return doUpdate(vh,this, binder.handlers)
inline fun <reified T> createPayload(lh: T, rh: T): Payload<T>
val clz = T::class as KClass<Any>
val changed: List<KProperty1<Any, *>> = clz.memberProperties.filter
it.get(lh as Any) != it.get(rh as Any)
return Payload(rh, changed as List<KProperty1<T, *>>)
private fun <T,VH : RecyclerView.ViewHolder> doUpdate(
vh: VH,
payload: Payload<T>,
handlers: Map<KProperty1<T, *>, (VH,Any?) -> Unit>,
)
val (state, changedProps) = payload
for (prop in changedProps)
val handler = handlers[prop]
if (handler == null)
print("not handle with $prop.name change.")
continue
val newValue = prop.get(state)
handler(vh,newValue)
以上是关于DiffUtils 遇到 Kotlin,榨干视图局部刷新的最后一滴性能的主要内容,如果未能解决你的问题,请参考以下文章