MultiType库实现原理分析

178 阅读3分钟

MultiType 是一个非常简洁灵活好用的RecyclerView#Adapter的第三方封装库,它能轻松实现多类型列表布局,且只需要关注数据类型、列表布局及数据更新逻辑,无需写其他的样板代码,真的很香。 GitHub地址: MultiType
GitHub给出的已实现的效果图如下:

multitype_github_shot.png

基本使用

  1. 创建类型布局对应的ItemBuilder
class OnlyTitleItemBuilder(private val clickBehavior: ((item: String, view: View) -> Unit)?): ItemViewBinder<String, BindingViewHolder<ItemOnlyTitleBinding>>() {

    override fun onCreateViewHolder(
        inflater: LayoutInflater,
        parent: ViewGroup
    ): BindingViewHolder<ItemOnlyTitleBinding> {
        return BindingViewHolder(
            ItemOnlyTitleBinding.inflate(inflater, parent, false)
        )
    }

    override fun onBindViewHolder(holder: BindingViewHolder<ItemOnlyTitleBinding>, item: String) {
        holder.itemView.setOnClickListener {
            clickBehavior?.invoke(item, it)
        }
        holder.binding.tvTitle.text = item
    }
}

class ImageTextItemBuilder(private val clickBehavior: ((item: ImageTextData, view: View) -> Unit)?)
    : ItemViewBinder<ImageTextData, BindingViewHolder<ItemImageTextBinding>>() {

    override fun onCreateViewHolder(
        inflater: LayoutInflater,
        parent: ViewGroup
    ): BindingViewHolder<ItemImageTextBinding> {
        return BindingViewHolder(
            ItemImageTextBinding.inflate(inflater, parent, false)
        )
    }

    override fun onBindViewHolder(holder: BindingViewHolder<ItemImageTextBinding>, item: ImageTextData) {
        holder.itemView.setOnClickListener {
            clickBehavior?.invoke(item, it)
        }
        holder.binding.ivImage.setImageURI(item.imageUrl)
        holder.binding.tvTitle.text = item.title
    }
}
  1. 将ItemBuilder注册到MultiTypeAdapter中
    private val multiTypeAdapter by lazy { 
        MultiTypeAdapter() 
    } 
    
    private fun setupMultiTypeList() { 
        multiTypeAdapter.register(OnlyTitleItemBuilder() { item, view -> 
        }) 
        multiTypeAdapter.register(ImageTextItemBuilder() { item, view -> 
        }) 
        val linearLayoutManager = LinearLayoutManager(this,LinearLayoutManager.VERTICAL, false)
        binding.multitypeList.layoutManager = linearLayoutManager
        binding.multitypeList.adapter = multiTypeAdapter 
        binding.multitypeList.itemAnimator = null 
   }
  1. 更新数据到MultiTypeAdapter
    private val items = mutableListOf<Any>() 
    private fun loadData() { 
        items.add("Item Title 1") 
        items.add("Item Title 2") 
        items.add(ImageTextData( 
            TEST_IMAGE_YUGI_URL, 
            "Item Title 3" 
        )) 
        items.add(ImageTextData( 
            TEST_IMAGE_JUNNING_URL, 
            "Item Title 4" 
        )) 
        multiTypeAdapter.items = items 
        multiTypeAdapter.notifyDataSetChanged() 
   }

上面demo代码运行效果如下:

multitype_adapter_demo.jpg

实现原理分析

  1. 主要思路
    MultiType库的核心类是MultiTypeAdapter
    自定义RecyclerView.Adapter的子类,通过register方法关联viewType值与delegate,
    重写 getItemViewType 方法获取自定义viewType,这样在onCreateViewHolderonBindViewHolder方法中,就可以根据viewType去调用对应ItemViewDelegate的onCreateViewHolder,onBindViewHolder方法,以完成viewHolder的创建及刷新。

  2. 源码实现
    a. 注册viewType

        inline fun <reified T : Any> register(binder: ItemViewBinder<T, *>) {
          register(binder as ItemViewDelegate<T, *>)
        }
    
        fun <T> register(clazz: Class<T>, delegate: ItemViewDelegate<T, *>) {
          unregisterAllTypesIfNeeded(clazz)
          //注释1:将clazz与delegate封装为Type,并保存到List中
          register(Type(clazz, delegate, DefaultLinker()))
        }
    
        internal fun <T> register(type: Type<T>) {
          //注释2:types是list类型,这里相当于list#add方法
          types.register(type)
          type.delegate._adapter = this
        }
        
        data class Type<T>( 
            val clazz: Class<out T>, 
            val delegate: ItemViewDelegate<T, *>, 
            val linker: Linker<T> 
        )
    

    register方法,最后会将数据类型clazz与ItemViewDelegate封装为Type类型并保存到列表(types)中,如注释1和注释2所示。

    b. 重写getItemViewType

         override fun getItemViewType(position: Int): Int {
           return indexInTypesOf(position, items[position])
         }
         
         @Throws(DelegateNotFoundException::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
             //注释3:register方法默认设置的是DefaultLinker,linker#index方法会返回0 
             //所以实际这里返回的是item对应类型的下标
             return index + linker.index(position, item)
           }
           throw DelegateNotFoundException(item.javaClass)
         }
    

    核心代码是 val index = types.firstIndexOf(item.javaClass), getItemViewTyp方法实际获取的是item类型对应的列表下标。
    本质上就是,在register方法注册delegate时,delegate注册时对应的index,就是它的viewType。
    后续的onCreateViewHolder、onBindViewHolder方法,都需要根据viewType找到对应的delegate,然后调用delegate对应的方法。

    c. 重写onCreateViewHolder方法

       //RecyclerView内部调用onCreateViewHolder方法前会先调用getItemViewType方法 
       override fun onCreateViewHolder(parent: ViewGroup, indexViewType: Int): ViewHolder {   
           return types.getType(indexViewType)     
               //根据viewType获取自定义的ItemViewDelegate
               .delegate
               //调用自定义ItemViewDelegate的onCreateViewHolder方法
               .onCreateViewHolder(parent.context, parent)
           } 
    

    在onCreateViewHolder方法中,通过在getItemViewType方法中获取的indexViewType,在列表(types)中取出对应的delegate, 调用delegate#onCreateViewHolder方法,走到自定义的viewHolder创建逻辑。

    d. 重写onBindViewHolder方法

        override fun onBindViewHolder(holder: ViewHolder, position: Int) {   
            onBindViewHolder(holder, position, emptyList()) 
        } 
        
        override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: List) {   
            val item = items[position]   
            getOutDelegateByViewHolder(holder).onBindViewHolder(holder, item, payloads) 
       } 
       
       private fun getOutDelegateByViewHolder(holder: ViewHolder): ItemViewDelegate<Any, ViewHolder> {   
           //根据viewType查找自定义ItemViewDelegate     
           @Suppress("UNCHECKED_CAST")     
           return types.getType<Any>(holder.itemViewType)       
               .delegate as ItemViewDelegate<Any, ViewHolder> 
       }
    

    ViewHolder#itemViewType属性是在RecyclerView#createViewHolder方法中设置的,属性值就是getItemViewType方法的返回值,具体参见RecycerView的源码,关键代码如下:

        @Nullable ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
            ...... 
            final int type = mAdapter.getItemViewType(offsetPosition); 
            ...... 
            holder = mAdapter.createViewHolder(RecyclerView.this, type); 
            ...... 
        } 
        
        public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) { 
            try { 
                TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); 
                final VH holder = onCreateViewHolder(parent, viewType); 
                if (holder.itemView.getParent() != null) { 
                    throw new IllegalStateException("xxx"); 
                } 
                holder.mItemViewType = viewType;//设置viewType return holder; 
            } finally { 
                TraceCompat.endSection(); 
            } 
        }
    

    所以在onBindViewHolder方法中,同样是通过getItemViewType获取的indexViewType, 然后在列表(types)中取出对应的delegate, 调用delegate#onBindViewHolder方法,走到自定义的绑定viewHolder数据的逻辑。

以上就是所有内容,有不对的地方,欢迎留言交流。