MultiType 是一个为 RecycleView 创建多种 Item 的 Android 库,它的设计简洁优雅,源码阅读体验也很好,本文记录了笔者研读此项目源码的感悟。

MultiType 的简单使用

开始之前,先来看下 MultiType 的总体类图

MultiType 类图

图中左侧 MultiTypeAdapter 是供外部访问的主要类,它的使用方式如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class SampleActivity : AppCompatActivity() {

  private lateinit var adapter: MultiTypeAdapter
  private lateinit var items: MutableList<Any>

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_list)
    val recyclerView = findViewById<RecyclerView>(R.id.list)
    // 实例化MultiTypeAdapter
    adapter = MultiTypeAdapter()
    // 注册三种类型的 Item 
    adapter.register(TextItemViewBinder())
    adapter.register(ImageItemViewBinder())
    adapter.register(RichItemViewBinder())
    recyclerView.adapter = adapter
    // 模拟数据
    val textItem = TextItem("world")
    val imageItem = ImageItem(R.mipmap.ic_launcher)
    val richItem = RichItem("小艾大人赛高", R.drawable.img_11)
    items = ArrayList()
    for (i in 0..19) {
      items.add(textItem)
      items.add(imageItem)
      items.add(richItem)
    }
    adapter.items = items
    adapter.notifyDataSetChanged()
  }
}

图中ItemViewBinder 是一个负责创建 ItemView 与填充 Item 数据的抽象类,上述代码中的 TextItemViewBinderImageItemViewBinderRichItemViewBinder 均为ItemViewBinder的实现类。

ItemViewBinder的代码看起来很直观,以ImageItemViewBinder为例,它的实现如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class ImageItemViewBinder : ItemViewBinder<ImageItem, ImageItemViewBinder.ImageHolder>() {
  inner class ImageHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val image: ImageView = itemView.findViewById(R.id.image)
  }
  override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ImageHolder {
    return ImageHolder(inflater.inflate(R.layout.item_image, parent, false))
  }
  override fun onBindViewHolder(holder: ImageHolder, item: ImageItem) {
    holder.image.setImageResource(item.resId)
  }
}

可以看到,MultiType 将 Item 视图创建与数据填充的工作交给了 ItemViewBinder来完成。当有新类型的 ItemView 需求时,只需先创建一个继承于 ItemViewBinder的具体类,再将其注册到 MultiTypeAdapter 中即可。

MultiType 源码解析

1. MultiTypeAdaprer 的 register 过程

MultiTypeAdapter 入手,它有如下三个 成员变量。

1
2
3
open var items: List<Any> = emptyList() //  Adapter 内的 item 数据列表
open val initialCapacity: Int = 0       //  MutableTypes 的初始容量
open var types: Types = MutableTypes(initialCapacity) // 表示多种 Item 类型的容器

以文章开头处的代码为例,先来看一下 MultiTypeAdapter的成员函数 register 的执行流程

title: MultiTypeAdapter register 工作流程
Client->MultiTypeAdapter: 1. register(ItemViewBinder<T,*>)
MultiTypeAdapter->MultiTypeAdapter: 2. register(Class<T>,ItemViewBinder<T,*>)
MultiTypeAdapter->MultiTypeAdapter: 3. register(Type<T>)
MultiTypeAdapter->MutableTypes: 4. register(Type<T>)

1.1 register(binder: ItemViewBinder<T, *>)

1
2
3
inline fun <reified T : Any> register(binder: ItemViewBinder<T, *>) {
  register(T::class.java, binder)
}

函数中将 T 声明为 refied ,表示 T 是一个具体的类型,随后在函数内部调用了同名重载函数 register(clazz: Class<T>, binder: ItemViewBinder<T, *>)

1.2 register(clazz: Class, binder: ItemViewBinder<T, *>)

1
2
3
4
fun <T> register(clazz: Class<T>, binder: ItemViewBinder<T, *>) {
  unregisterAllTypesIfNeeded(clazz)
  register(Type(clazz, binder, DefaultLinker()))
}

若已使用 clazz 注册过,则先对其进行清除。接着将前面传递来的参数 clazzbinder 与新建的DefaultLinker 对象 一起作为 Type 构造函数的参数,构建 Type 对象。然后调用同名重载函数register(type:Type<T>)

Type 是一个只有三个属性的 data class,它的实现如下

1
2
3
4
5
data class Type<T>(
  val clazz: Class<out T>,
  val binder: ItemViewBinder<T, *>,
  val linker: Linker<T>
)

1.3 register(type: Type)

1
2
3
4
internal fun <T> register(type: Type<T>) {
  types.register(type)
  type.binder._adapter = this
}

此处将上一步传来的 type对象 注册到 MutableTypes类型的成员变量 types 中,同时将 MultiTypeAdapter 自身的引用赋值给 type对象 中成员变量 binder 的属性 _adapter ,便于后续的使用。

接下来看 MutableTypesregister 函数的实现

1.4 MutableTypes.register(type:Type)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
open class MutableTypes constructor(
  open val initialCapacity: Int = 0,
  open val types: MutableList<Type<*>> = ArrayList(initialCapacity)
) : Types {
......
  override fun <T> register(type: Type<T>) {
    types.add(type)
  }
  override fun unregister(clazz: Class<*>): Boolean {
    return types.removeAll { it.clazz == clazz }
  }
......
}

MutabaleTypes 有属性 initialCapacitytypes ,前者表示后者的默认容器大小,types 则是一个可变列表。另外还可以看到,MutbaleTypes 实现了 Types 接口,并重写了register 函数,此函数是将传入的参数 type 添加到 types 列表中。与 register 函数相反的还有另一个重写函数 unregister ,它是将某个指定的 Class 所对应的全部 Type 对象types 列表中移除。

1.5小结

结合以上四步操作,可知 MultiTypeAdapterregister 函数实际是将待注册的 数据模型 Class 信息数据模型对应的 ItemViewBinder数据模型对应的 Linker(默认为 DefaultLinker) 组合为 Type 对象,然后将其添加到 MultiTypeAdapter 的属性 types 对应的MutableTypes 对象的属性 types 可变数组中,以供后续使用。

2. MultiTypeAdapter 中 ViewHolder 创建与数据填充的过程

MultiTypeAdapterRecyclerView.Adapter 的实现类,RecyclerView.Adapter 中与 ViewHolder 创建及数据填充的相关的函数主要有以下四个:

  1. getItemViewType(position: Int): Int // 获取每一个 Item 对应的 View 类型
  2. onCreateViewHolder(parent: ViewGroup, indexViewType: Int): ViewHolder // 为特定类型的 Item 创建 ViewHolder
  3. onBindViewHolder(holder: ViewHolder, position: Int) //为每一个 Item 进行数据填充
  4. getItemCount(): Int // 获取 Adapter 内 Item 的数量

我们主要观察 MultiTypeAdapter 中前三个函数的实现

2.1 getItemViewType(position: Int): Int

MultiTypeAdapter->MultiTypeAdapter: 1.indexInTypesOf
MultiTypeAdapter->MutableTypes: 2.firstIndexOf
MutableTypes-->MultiTypeAdapter: index
MultiTypeAdapter->MutableTypes: 3.getType
MutableTypes->MutableTypes: getLinker
MutableTypes-->MultiTypeAdapter: linker
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
open class MultiTypeAdapter @JvmOverloads constructor(
  open var items: List<Any> = emptyList(),
  open val initialCapacity: Int = 0,
  open var types: Types = MutableTypes(initialCapacity)
) : RecyclerView.Adapter<ViewHolder>() {
  ......
  override fun getItemViewType(position: Int): Int {
      return indexInTypesOf(position, items[position])
  }
  ......
  @Throws(BinderNotFoundException::class)
  internal fun indexInTypesOf(position: Int, item: Any): Int {
    val index = types.firstIndexOf(item.javaClass)
    if (index != -1) {
      val linker = types.getType<Any>(index).linker
      return index + linker.index(position, item)
    }
    throw BinderNotFoundException(item.javaClass)
  }
  ......
}

先来看 getItemViewType 函数, 第8行代码,调用了 indexInTypesOf 函数,传递给后者的参数为当前 item 在列表中的位置 position 以及这个 item 对象。

再来看 indexInTypesOf 函数,第13行代码,成员变量 typesfirstIndexOf 函数通过 item 的 Class 信息查询这个 itemtypes 中首次出现的索引值,若得到的索引值不是 -1 ,则表示表示与此 item 的 Class 信息相关联的 Type 对象已注册到 MutbleTypes 中;若得到的索引值是 -1 ,则表示与此 item 的 Class 想关联的 Type 对象未注册,并在第18行抛出自定义的运行时异常BinderNotFoundExceptiontypes 是类型为 MutableTypes 的对象,MutableTypesfirstIndexOf 函数的实现如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
open class MutableTypes constructor(
  open val initialCapacity: Int = 0,
  open val types: MutableList<Type<*>> = ArrayList(initialCapacity)
) : Types {
  ......
  override fun firstIndexOf(clazz: Class<*>): Int {
    val index = types.indexOfFirst { it.clazz == clazz }
    if (index != -1) {
      return index
    }
    return types.indexOfFirst { it.clazz.isAssignableFrom(clazz) }
  }
  ......
}

MutbaleTypesfirstIndexOf 函数的形参是 Class 类型的 clazz 对象,第7行代码,在成员变量 types 列表中查找与 clazz 属性与形参一致的首个 Type 对象的索引值。若能查到(不为 -1),则直接返回此索引;若未查到,则尝试使用入参 clazz 的继承关系重新在成员变量 types 内查询一次,其中 A.isAssignableFrom(B) 表示判断AClass 信息是否为 BClass 信息的接口或父类。

回到 indexInTypesOf 函数,第16行代码,返回上一步得到的索引值 indexlinker.index(posotion,item) 相加的值,linker 的默认实现为 DefaultLinker,其 index 函数的返回值始终为 0 ,因此此处相加的值即为上一步得到的索引值 index,对于 linker 的特殊实现我们稍后在第三小节单独分析,此处暂且不表。

至此,我们知道 getItemViewType 函数的返回值 viewType 即为当前 item 的 Class 信息在 MutableTypes 的成员变量 types 列表中索引值。

2.2 onCreateViewHolder(parent: ViewGroup, indexViewType: Int): ViewHolder

MultiTypeAdapter->LayoutInflater: 1. from
LayoutInflater-->MultiTypeAdapter: inflater
MultiTypeAdapter->MutableTypes: 2. getType
MutableTypes->MutableTypes: 3. getBinder
MutableTypes-->MultiTypeAdapter: binder
MultiTypeAdapter->ItemViewBinder:4. onCreateViewBinder
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
open class MultiTypeAdapter @JvmOverloads constructor(
  open var items: List<Any> = emptyList(),
  open val initialCapacity: Int = 0,
  open var types: Types = MutableTypes(initialCapacity)
) : RecyclerView.Adapter<ViewHolder>() {
  ......
  override fun onCreateViewHolder(parent: ViewGroup, indexViewType: Int): ViewHolder {
    val inflater = LayoutInflater.from(parent.context)
    val binder = types.getType<Any>(indexViewType).binder
    return binder.onCreateViewHolder(inflater, parent)
  }
  ......
}

当 RecyclerView 需要一个新的 ViewHolder 时,onCreateViewHolder 函数会被调用,传入的 parent 表示RecycleView ,indexViewType 表示这个 item 对应的视图类型,即在 getItemViewType 函数的返回值

第8行代码从传入的 ViewGroup 类型的 parent 参数中获取 context 对象,然后将此 context 作为参数调用 LayoutInflater 的静态方法 from ,得到 Inflater 类型 inflater 的对象。

第9行代码,通过传入的 indexViewType 参数,先调用属性 typesgetType 函数获取 Type 类型的对象,然后得到 Type 对象的属性 bindergetType 函数的实现如下所示,只是将 index 对应的 Type 从 types 列表中取出,这个 Type 对象是在初始化时通过 register 函数添加到 types 列表中的,我们在第一小节中有介绍。

1
2
3
4
5
6
7
8
9
open class MutableTypes constructor(
  open val initialCapacity: Int = 0,
  open val types: MutableList<Type<*>> = ArrayList(initialCapacity)
) : Types {
  ......
  @Suppress("UNCHECKED_CAST")
  override fun <T> getType(index: Int): Type<T> = types[index] as Type<T>
  ......
}

第10行代码,调用上一步得到的 binder 对象的成员函数 onCreateViewHolder 来完成 ViewHolder 的创建。

至此,我们了解了 ViewHolder 的创建过程。

2.3 onBindViewHolder(holder: ViewHolder, position: Int)

MultiTypeAdapter->MultiTypeAdapter: 1. onBindViewHolder
MultiTypeAdapter->MultiTypeAdapter: 2. onBindViewHolder
MultiTypeAdapter->MultiTypeAdapter: 3. getOutBinderByViewHolder
MultiTypeAdapter->MutableTypes: 4. getType
MutableTypes-->MultiTypeAdapter: type
MultiTypeAdapter->Type: 5.getBinder
Type-->MultiTypeAdapter : binder
MultiTypeAdapter->ItemViewBinder: 6. onBindViewHolder
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
open class MultiTypeAdapter @JvmOverloads constructor(
  open var items: List<Any> = emptyList(),
  open val initialCapacity: Int = 0,
  open var types: Types = MutableTypes(initialCapacity)
) : RecyclerView.Adapter<ViewHolder>() {
  ......
  override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    onBindViewHolder(holder, position, emptyList())
  }

  override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: List<Any>) {
    val item = items[position]
    getOutBinderByViewHolder(holder).onBindViewHolder(holder, item, payloads)
  }
  ......
  
  private fun getOutBinderByViewHolder(holder: ViewHolder): ItemViewBinder<Any, ViewHolder> {
    @Suppress("UNCHECKED_CAST")
    return types.getType<Any>(holder.itemViewType).binder as ItemViewBinder<Any, ViewHolder>
  }
}

当 RecyclerView 需要显示指定 position 位置的 Item 时,MultiTypeAdapteronBindViewHolder 函数会被调用。

第7-9行代码,函数的形参 holder 是在 onCreateViewHolder 函数中创建的,position 是当前 Item 对应的位置。第8行代码调用了同名重载函数。

第11-14行代码,函数的形参中多了一个用于优化数据填充的 payloads 参数。第12行,先获取 position 对应的 item 对象。第13行,先通过成员函数 getOutBinderByViewHolder 获取具体的 ItemViewBinder 对象。

getOutBinderByViewHolder 函数的实现也很直观。第19行代码,先通过MultiTypes类型的属性 types 的成员函数 getType 得到函数形参传递而来的 holder 对象的 itemViewType 属性所对应的 Type对象,然后将此Type对象的 binder 属性强转为 ItemVIewBinder 返回给调用方。

回到 onCreateViewHolder 函数,第13行代码,在得到当前 holder 对应的ItemViewBinder之后,接着便调用了这个ItemViewBinder对象的成员函数 onBindViewHolder,伴随的参数的是 holderitem与上一步传来的 payloads,从而完成了这个 ViewHolder 的数据填充。

2.4 小结

第一小节中,MultiTypeAdapterregister 函数先将数据模型的 Class 信息具体的 ItemViewBinder 以及 linker 封装为 Type 对象并添加到 MutbaleTypes 的成员变量 types 列表中。

本小节中解析的三个函数,均与第一小节中介绍的 MutableTypes 有关。

  • getItemViewType 函数根据当前 item 的 Class 信息获取其所对应的 Type 对象在 types 列表中的索引值;
  • onCreateViewHolder 函数根据 getItemViewType 计算出的索引值在 types 列表中获取具体 ItemViewBinder, 然后使用调用这个 ItemViewBinder 的 onCreateViewHolder 函数创建具体的 ViewHolder;
  • onBindViewHolder 函数根据 onCreateViewHolder 中创建的 holder 对象的成员变量 itemViewType 获取这个 holder 对应的 Type 对象的成员变量 binder,然后调用 binder 的 onBindViewHolder 完成数据填充。

3. MultiType 一对多

MultiType 中的一对多是指 RecyclerView 中的同一份数据模型,对应多种 ViewHolder。如下所示,电商 App 的搜索结果页就属这种情况。

列表模式大图模式

3.1 one2many 简单实用

下边以 MultiType 中的sample one2many 为入口,来了解一下 MultiType 是如何实现一对多的。开始之前,先来看一下 one2many 的运行截图。

one2many sample 中包含以下四个类,其中 Data 是数据模型类,它有 DataType1ViewBinderDataType2ViewBinder 两种样式。OneDataToManyActivity 是这个 sample 的 Activity。

one2many
  Data.kt
  DataType1ViewBinder.kt
  DataType2ViewBinder.kt
  OneDataToManyActivity.kt

Data 类的代码如下,它有 titletype 两个成员变量,前者用来显示,后者表示其对应的 ViewBinder 类型。

1
2
3
4
5
6
7
8
9
class Data(
  @field:SerializedName("title") var title: String,
  @field:SerializedName("type") var type: Int
) {
  companion object {
    const val TYPE_1 = 1
    const val TYPE_2 = 2
  }
}

DataType1ViewBinderDataType2ViewBinder 的区别仅是 layout 不同,如下所示。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
- class DataType1ViewBinder : ItemViewBinder<Data, DataType1ViewBinder.ViewHolder>() {
+ class DataType2ViewBinder : ItemViewBinder<Data, DataType2ViewBinder.ViewHolder>() {

  override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
-    return ViewHolder(inflater.inflate(R.layout.item_data_type1, parent, false))
+    return ViewHolder(inflater.inflate(R.layout.item_data_type2, parent, false))    
  }

  override fun onBindViewHolder(holder: ViewHolder, item: Data) {
    holder.setTitle(item.title)
  }

  class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

    private var titleView: TextView = itemView.findViewById(android.R.id.title)

    fun setTitle(title: String) {
      titleView.text = title
    }
  }
}

R.layout.item_data_type1 与 R.layout.item_data_type2 的区别仅是 gravity 不同,如下所示。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
-    android:gravity="start"> //R.layout.item_data_type1
+    android:gravity="end">   //R.layout.item_data_type2

    <TextView
        android:id="@android:id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="12dp"
        android:hint="right"
        tools:text="title"/>

</LinearLayout>

OneDataToManyActivity 中的代码如下,其 register 部分与第一小节有明显不同。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class OneDataToManyActivity : MenuBaseActivity() {

  @VisibleForTesting
  lateinit var recyclerView: RecyclerView
  @VisibleForTesting
  lateinit var adapter: MultiTypeAdapter

  private val dataFromService: List<Data>
    @VisibleForTesting
    get() {
      val list = ArrayList<Data>()
      var i = 0
      while (i < 30) {
        list.add(Data("title: $i", Data.TYPE_1))
        list.add(Data("title: ${ i + 1 }", Data.TYPE_2))
        i += 2
      }
      return list
    }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_list)
    recyclerView = findViewById(R.id.list)
    adapter = MultiTypeAdapter()

    adapter.register(Data::class).to(
      DataType1ViewBinder(),
      DataType2ViewBinder()
    ).withKotlinClassLinker(object :KotlinClassLinker<Data>{
      override fun index(position: Int, data: Data): KClass<out ItemViewBinder<Data, *>> {
        return when (data.type) {
          Data.TYPE_2 -> DataType2ViewBinder::class
          else -> DataType1ViewBinder::class
        }
      }
    })

    adapter.items = dataFromService
    adapter.notifyDataSetChanged()
    recyclerView.adapter = adapter
  }
}

3.2 one2many 的 register 过程

Client->MultiTypeAdapter: 1. register
MultiTypeAdapter->MultiTypeAdapter: 2. register
MultiTypeAdapter-->Client: oneToManyBuilder
Client->OneToManyBuilder: 3. to
Client->OneToManyBuilder: 4. withKotlinClassLinker
OneToManyBuilder->OneToManyEndpoint: 5. withKotlinClassLinker
OneToManyEndpoint->OneToManyEndpoint: 6. withJavaClassLinker
OneToManyEndpoint->OneToManyBuilder: 7. withJavaClassLinker
OneToManyBuilder->ClassLinkerBridge: 8.toLinker
ClassLinkerBridge-->OneToManyBuilder: linkerBridge
OneToManyBuilder->OneToManyBuilder: 9. withLinker
OneToManyBuilder->OneToManyBuilder: 10. doRegister
note left of OneToManyBuilder: loop: binders
OneToManyBuilder->MultiTypeAdapter: 11. register

MultiTypeAdapter 一对多的 register 函数实现如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
open class MultiTypeAdapter @JvmOverloads constructor(
  open var items: List<Any> = emptyList(),
  open val initialCapacity: Int = 0,
  open var types: Types = MutableTypes(initialCapacity)
) : RecyclerView.Adapter<ViewHolder>() {
  ......
  @CheckResult
  fun <T : Any> register(clazz: KClass<T>): OneToManyFlow<T> {
    return register(clazz.java)
  }

  @CheckResult
  fun <T> register(clazz: Class<T>): OneToManyFlow<T> {
    unregisterAllTypesIfNeeded(clazz)
    return OneToManyBuilder(this, clazz)
  }
  ......
}

register 函数,第9行代码,获取调用方传来的 KClass 对象的 javaClass 变量,然后调用同名重载函数。第14行代码,尝试移除此 javaClass 已经关联的 Type。第15行代码,创建 OneToManyBuilder 对象并返回给调用方。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    adapter.register(Data::class).to(
      DataType1ViewBinder(),
      DataType2ViewBinder()
    ).withKotlinClassLinker(object :KotlinClassLinker<Data>{
      override fun index(position: Int, data: Data): KClass<out ItemViewBinder<Data, *>> {
        return when (data.type) {
          Data.TYPE_2 -> DataType2ViewBinder::class
          else -> DataType1ViewBinder::class
        }
      }
    })

回到 OneDataToManyActivityonCreate 方法。第1行代码,在调用 MultiTypeAdapterregister 函数后得到了 OneToManyBulder 对象,紧接着调用该对象的 to 函数,并将 DataType1ViewBinder(), DataType2ViewBinder() 作为参数传入。第30-37行代码,调用 OneToManyBulder 对象的 withKotlinClassLinker 函数,并新建了一个 KotlinClassLinker 的匿名内部类的对象作为此函数的实际参数。

接下来我们来看下 OneToManyBulder 的代码,它实现了 OneToManyFlowOneToManyEndpoint 这两个接口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
internal class OneToManyBuilder<T>(
  private val adapter: MultiTypeAdapter,
  private val clazz: Class<T>
) : OneToManyFlow<T>, OneToManyEndpoint<T> {

  private var binders: Array<ItemViewBinder<T, *>>? = null

  @SafeVarargs
  @CheckResult(suggest = "#withLinker(Linker)")
  override fun to(vararg binders: ItemViewBinder<T, *>) = apply {
    @Suppress("UNCHECKED_CAST")
    this.binders = binders as Array<ItemViewBinder<T, *>>
  }

  override fun withLinker(linker: Linker<T>) {
    doRegister(linker)
  }

  override fun withJavaClassLinker(javaClassLinker: JavaClassLinker<T>) {
    withLinker(ClassLinkerBridge.toLinker(javaClassLinker, binders!!))
  }

  private fun doRegister(linker: Linker<T>) {
    for (binder in binders!!) {
      adapter.register(Type(clazz, binder, linker))
    }
  }
}

-----------------接口代码--------------------
interface OneToManyFlow<T> {
  @CheckResult
  fun to(vararg binders: ItemViewBinder<T, *>): OneToManyEndpoint<T>
}
interface OneToManyEndpoint<T> {
  fun withLinker(linker: Linker<T>)

  fun withLinker(linker: (position: Int, item: T) -> Int) {
    withLinker(object : Linker<T> {
      override fun index(position: Int, item: T): Int {
        return linker(position, item)
      }
    })
  }

  fun withJavaClassLinker(javaClassLinker: JavaClassLinker<T>)

  private fun withJavaClassLinker(classLinker: (position: Int, item: T) -> Class<out ItemViewBinder<T, *>>) {
    withJavaClassLinker(object : JavaClassLinker<T> {
      override fun index(position: Int, item: T): Class<out ItemViewBinder<T, *>> {
        return classLinker(position, item)
      }
    })
  }

  fun withKotlinClassLinker(classLinker: KotlinClassLinker<T>) {
    withJavaClassLinker { position, item -> classLinker.index(position, item).java }
  }

  fun withKotlinClassLinker(classLinker: (position: Int, item: T) -> KClass<out ItemViewBinder<T, *>>) {
    withJavaClassLinker { position, item -> classLinker(position, item).java }
  }
}

OneToManyBuilder 有 adapterclazz 两个属性,如之前所述,这两个属性在 MultiTypeAdapterregister 函数中被赋值。

第10行代码,to(vararg binders: ItemViewBinder<T, *>) 函数声明中的的 vararg关键字,对应 Java 中的可变参数符号 ,此处表示 to 函数可以接受一个或多个 ItemViewBinder 类型的对象作为参数。to 函数的实现是将调用方传入的参数 binders 赋值给 OneToManyBuilder 的属性 binders

OneToManyBuilder并未重写 withKotlinClassLinker 函数,因此当调用方调用此函数时,会调用到OneToManyEndpoint接口内的默认函数 withKotlinClassLinker。此函数有两个重载版本,一个接受 KotlinClassLinker 类型的对象作为参数,另一个接受(position: Int, item: T) -> KClass<out ItemViewBinder<T, *>>)类型的 lambda 变量作为参数,这两个默认函数内部都会调用第48行的默认函数 withJavaClassLinker 来将上层调用方传来的 KotlinClassLinker 类型的对象转换为 JavaClassLinker 类型的对象。

默认函数 withJavaClassLinker 内部又调用了同名重载函数 withJavaClassLinker ,并将新建的JavaClassLinker 对象作为参数传递过去。

OneToManyBuilder重写了这个重载的 withJavaClassLinker 函数,第19行代码,先将上一步传来的 javaClassLinker 与它的 binders 属性作为参数,调用了 ClassLinkerBridge 的静态函数 toLinker 将其转化为ClassLinkerBridge 类型的对象,ClassLinkderBridge的实现如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
internal class ClassLinkerBridge<T> private constructor(
  private val javaClassLinker: JavaClassLinker<T>,
  private val binders: Array<ItemViewBinder<T, *>>
) : Linker<T> {

  override fun index(position: Int, item: T): Int {
    val indexedClass = javaClassLinker.index(position, item)
    for (i in binders.indices) {
      if (binders[i].javaClass == indexedClass) {
        return i
      }
    }
    throw IndexOutOfBoundsException(
      "The binders'(${binders.contentToString()}) you registered do not contain this ${indexedClass.name}."
    )
  }

  companion object {
    fun <T> toLinker(
      javaClassLinker: JavaClassLinker<T>,
      binders: Array<ItemViewBinder<T, *>>
    ): Linker<T> {
      return ClassLinkerBridge(javaClassLinker, binders)
    }
  }
}

ClassLinkerBridge实现了 Linker 接口并重写了此接口的 index 函数。第7行代码,通过调用方传入的 javaClssLinker 参数,获取 positionitem 所对应的 ItemViewBinderClass 信息。第8-12 行代码,获取这个 Class 对应的 ItemViewBinder 在属性 binders 列表中的索引值,若查找不到,则抛出自定义异常 IndexOutOfBoundsException

回到OneToManyBuilder中,第19行代码,将上一步得到 ClassLinkerBridge 对象作为参数调用了第15行的withLinker 函数,此函数内部调用了第23行的 doRegister 函数。doRegister 函数内对OneToManyBuilder的属性 binders 进行遍历,并将其中的每一个 binderclazz 属性和传入的参数linker组合为 Type 对象,注册到 MutableTypes 中。因此对于同一份数据模型,会在 MutableTypes 中注册多个 Type。此处索引值的设计思路非常巧妙,随后会单独讨论。

3.3 one2many 中 ViewHolder 的创建

Client->MultiTypeAdpter: 1. getItemViewType
MultiTypeAdpter->MultiTypeAdpter: 2. indexInTypesOf
MultiTypeAdpter->MutableTypes: 3. firstIndexOf
MutableTypes-->MultiTypeAdpter: firstIndex
MultiTypeAdpter->MutableTypes: 4. getType
MutableTypes->MutableTypes: getLinker
MutableTypes-->MultiTypeAdpter: linker
MultiTypeAdpter->ClassLinkerBridge: 5. index
ClassLinkerBridge-->MultiTypeAdpter: linker.index
MultiTypeAdpter-->Client: firstIndex + linker.index

通过前面的分析我们知道,RecyclerView 中 ViewHolder 的创建与 ItemViewType 有关,而 ItemViewType 源于 Adapter 中的 getItemViewType 函数,我们再来看一下MultiTypeAdaptergetItemViewType 的实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
override fun getItemViewType(position: Int): Int {
  return indexInTypesOf(position, items[position])
}
@Throws(BinderNotFoundException::class)
internal fun indexInTypesOf(position: Int, item: Any): Int {
  val index = types.firstIndexOf(item.javaClass)
  if (index != -1) {
    val linker = types.getType<Any>(index).linker
    return index + linker.index(position, item)
  }
  throw BinderNotFoundException(item.javaClass)
}

第8行代码,根据当前索引得到在 MutableTypes中注册的 Type 对象的属性 linker ,这个 linker 便是我们在 3.2中见到过的ClassLinkerBridge。第9行代码,调用这个 linker 对象的 index 函数,index 的实现如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
internal class ClassLinkerBridge<T> private constructor(
  private val javaClassLinker: JavaClassLinker<T>,
  private val binders: Array<ItemViewBinder<T, *>>
) : Linker<T> {
  override fun index(position: Int, item: T): Int {
    val indexedClass = javaClassLinker.index(position, item)
    for (i in binders.indices) {
      if (binders[i].javaClass == indexedClass) {
        return i
      }
    }
    throw IndexOutOfBoundsException(
      "The binders'(${binders.contentToString()}) you registered do not contain this ${indexedClass.name}."
    )
  }
  ......
}

第6行代码,index 函数调用了 JavaClassLinker 对象的 index 函数。JavaClassLinker 对象是在注册过程中经 withJavaClassLinker 函数转化而来的,此函数将调用方传入的 KotlinClassLinker类型的对象转换为JavaClassLinker类型的对象,我们在 3.2 中已做过分析,此处不再展开。转换前的 kotlinClassLinker 如下所示,根据传入的参数 data 的成员变量 type 来决定返回的ItemViewBinder的类型。

1
2
3
4
5
6
7
8
withKotlinClassLinker(object :KotlinClassLinker<Data>{
  override fun index(position: Int, data: Data): KClass<out ItemViewBinder<Data, *>> {
    return when (data.type) {
      Data.TYPE_2 -> DataType2ViewBinder::class
      else -> DataType1ViewBinder::class
    }
  }
})

one2many sample 中这样处理是有意演示 KotlinClass 的使用方式,平时用的话可以直接条用 withJavaClassLinker 来省略这一步的转换。

回到 ClassLinkerBridgeindex 函数,第6行代码得到了当前 Item 所对应的具体的Class<ItemViewBinder>对象。第7-11行代码,得到该 class 对象在属性 binders 数组中的首次出现的索引值,然后将其返回调用方。

再回头看 MultiTypeAdapterindexInTypesOf函数的实现。第9行代码,将这个 item 在 Types 中首次出现的索引值与它在 linker 的 binders 列表中的索引值相加,作为getItemViewType函数的返回值。

接下来是我们之前分析过的 onCreateViewHolder,这次我们主要看第3行代码,根据 getItemViewType 获取的 indexViewType ,得到其对应的 ItemViewBinder。

1
2
3
4
5
override fun onCreateViewHolder(parent: ViewGroup, indexViewType: Int): ViewHolder {
  val inflater = LayoutInflater.from(parent.context)
  val binder = types.getType<Any>(indexViewType).binder
  return binder.onCreateViewHolder(inflater, parent)
}

3.3 小结

之前说到 MultiType 中索引值的设计非常巧妙,在了解了一对多的注册、使用后,再来探索下这个巧妙之处。 one2many 的 sample 的注册过程中,在 MutableTypes 中注册了多个 Type对象,注册后 **types:List<Type>**的数据如下

indexClassItemViewBinderLinker
0DataDataType1ViewBinderClassLinkerBridge
1DataDataType2ViewBinderClassLinkerBridge

getItemViewType 中返回的索引值是当前位置的 Item 对应的 TypeMutableTypes的属性 types 列表中首次出现的索引值 index 与 这个索引值所对应的 Type 对象的 linker 属性的 index 函数的返回值 indexFromLinker 的和。

例1:Data 对应的 Typetypes中首次出现的索引值为 0,当Data对象的type属性为 TYPE_1 时,indexFromLinker 的值为 0,此时 getItemViewType 的返回值是 0。

onCreateViewHolder 函数的参数 indexViewType 即为上一步得到 indexindexFromLinker 和,此函数内调用 MutbaleTypes 类型属性 typesgetType 函数,将上一步得到的和作为 getType 的参数传递过去得到 Type对象,然后通过得到了这个Type对象的 binder属性。这个 binder 刚好和上一步中 ClassLinkerBridgeindex 函数中 javaClassLinker.index 所对应的 ItemViewBinder一致。

例2:传入的 indexViewType 值为 0,因此 **types.getType(indexViewType)**得到的Type 的即为表格中第一行的数据,它的 binder 属性即为 DataType1ViewBinder ,与例1中 Data 对象的 type 属性相匹配。

技术点拾遗

  1. MultiTypeAdapter 中重写了 fun onBindViewHolder(VH holder, int position, List<Object> payloads) 函数,此函数对 ViewHolder 的 部分数据更新 进行了优化。具体表现是当 payloads 列表不为空时,仅更新 payloads 内的部分数据,减轻渲染压力达到优化效果。

  2. 若要在 kotlin 泛型函数中使用泛型 T 的 Class 信息,需在 T 之前增加 reified 关键字,同时在函数前增加 inline 关键字,如下所示

1
2
3
4
inline fun <reified T : Any> register(binder: ItemViewBinder<T, *>) {
  // 函数内部使用 T 的 class 信息
  register(T::class.java, binder)
}
  1. kotlin 的构造函数自带的重载效果仅限 kotlin 模块可用,若要在 Java 模块中使用 kotlin 构造函数的重载函数,可在构造函数前添加@JvmOverloads注解,如下所示。
1
2
3
4
5
6
//class Demo(val param1:String,val param2:Int) 这种方式仅可在 kotlin 模块调用重载构造函数
class Demo{ 
  @JvmOverloads constructor(val param1:String, val param2:Int) 
}
-----------------------------------------------------------------
class Demo @JvmOverloads constructor(val param1:String, val param2:Int){}
  1. kotlin 中的泛型没有表示不确定类型的符号 ?,但有称为 Star-projections 的符号 * ,直译过来为星形投影。它是更为安全及严格的泛型限定符,编译器会将 <*> 视为 <out Any>,其中的 out 表示该类型只能作为函数的返回值类型使用,若将其作为函数的入参使用,编译器会直接报错。具体规则如下:
  • Function<*>表示 Function<in Nothing>Function<out Any?>
  • Function<*, String> 表示 Function<in Nothing, String>;
  • Function<Int, *> 表示 Function<Int, out Any?>;
  • Function<*, *> 表示 Function<in Nothing, out Any?>.