美文网首页自定义控件Android开发
如何用100行代码构建一个多样式RecyclerView适配器

如何用100行代码构建一个多样式RecyclerView适配器

作者: seagazer | 来源:发表于2020-07-24 16:43 被阅读0次
    • RecyclerView多样式布局的框架有很多,大家熟悉已久的BaseRecyclerViewAdapterHelpervlayout等等,包括google在新版本RecyclerView中推出的MergeAdapterContractAdapter等。既然有这么多现成的框架,为什么还要去自己编写一个呢?很多时候这些框架考虑的都是常见通用性场景,在某些奇葩的产品设计需求中也许并不适用,这也是这篇文章出现的原因。

    • 目前多样式适配器框架总体的设计方案有两种,一种是BaseRecyclerViewAdapterHelper这种极度简化开发者编写的代码量,用最简洁的方式去实现多样式。另外一种就是vlayout这种,将每种样式设计为单独模块(子适配器),视图创建,数据绑定都在该模块内部处理,再用一个主适配器将这些子适配器进行包装关联。本文采用的是第二种设计方式。

    由于代码量极少,下面就不多说了,直接贴代码:

    1. 视图构建器,保持原生api命名方式
    <ViewTypeCreator.kt>
    abstract class ViewTypeCreator<T, VH : ViewHolder> {
    
        abstract fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): VH
    
        abstract fun onBindViewHolder(holder: VH, data: T)
    
        abstract fun match(data: T): Boolean
    
        open fun getItemId(position: Int) = RecyclerView.NO_ID
    
    }
    
    1. 数据适配器
    <MultiTypeAdapter.kt>
    abstract class MultiTypeAdapter : RecyclerView.Adapter<ViewHolder>() {
        private val dataCache: ArrayList<Class<*>> = ArrayList()
        private val creatorCache: SparseArray<SparseArray<ViewTypeCreator<Any, *>>> = SparseArray()
        private val viewTypeCache: SparseArray<ViewTypeCreator<Any, *>> = SparseArray()
    
        abstract fun getData(position: Int): Any
    
        inline fun <reified T : Any> registerCreator(creator: ViewTypeCreator<T, *>) {
            registerCreatorInner(T::class.java, creator)
        }
    
        fun registerCreatorInner(clazz: Class<*>, creator: ViewTypeCreator<*, *>) {
            var index = dataCache.indexOf(clazz)
            if (index == -1) {
                dataCache.add(clazz)
                index = dataCache.size - 1
            }
            var cache = creatorCache[index]
            if (cache == null) {
                cache = SparseArray()
            }
            val id = System.identityHashCode(creator)
            @Suppress("UNCHECKED_CAST")
            cache.put(id, creator as ViewTypeCreator<Any, *>)
            creatorCache.put(index, cache)
        }
    
        override fun getItemViewType(position: Int): Int {
            val data = getData(position)
            val viewType = getCreatorViewType(data)
            return if (viewType != -1) {
                viewType
            } else
                super.getItemViewType(position)
        }
    
        override fun getItemId(position: Int): Long {
            val itemViewType = getItemViewType(position)
            val viewCreator = getViewCreatorByViewType(itemViewType)
            return viewCreator.getItemId(position)
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            val viewCreator: ViewTypeCreator<*, *> = getViewCreatorByViewType(viewType)
            return viewCreator.onCreateViewHolder(LayoutInflater.from(parent.context), parent)
        }
    
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            val data = getData(position)
            @Suppress("UNCHECKED_CAST")
            val viewCreator: ViewTypeCreator<Any, ViewHolder> =
                getViewCreatorByViewType(getItemViewType(position)) as ViewTypeCreator<Any, ViewHolder>
            viewCreator.onBindViewHolder(holder, data)
        }
    
        private fun getCreatorViewType(data: Any): Int {
            val clazz: Class<*> = data::class.java
            var viewType: Int
            val index = dataCache.indexOf(clazz)
            if (dataCache.size > 0 && index != -1) {
                val creators: SparseArray<ViewTypeCreator<Any, *>> = creatorCache[index]
                // The Data bind more than one viewTypeCreator.
                if (creators.size() > 1) {
                    creators.forEach { id, viewCreator ->
                        if (viewCreator.match(data)) {
                            viewType = id
                            if (viewTypeCache.indexOfKey(viewType) < 0) {
                                viewTypeCache.put(viewType, viewCreator)
                            }
                            return viewType
                        }
                    }
                }
                // The Data only bind one viewTypeCreator.
                else if (creators.size() == 1) {
                    viewType = creators.keyAt(0)
                    if (viewTypeCache.indexOfKey(viewType) < 0) {
                        viewTypeCache.put(viewType, creators.valueAt(0))
                    }
                    return viewType
                }
            }
            throw RuntimeException("Current dataType [$clazz] is not found in DataTypeCache:\n$dataCache \nPlease check the Type of data for your custom creator.")
        }
    
        private fun getViewCreatorByViewType(viewType: Int): ViewTypeCreator<Any, *> {
            return viewTypeCache[viewType]
        }
    }
    

    好了,代码就这么多,下面简单介绍下原理:

    1. 视图构建器不过多介绍,主要就是抽象出视图构建的方法,这里只重点说下match这个方法:
    fun match(data: T): Boolean
    
    • 这里面接收一个数据类型参数,需要返回一个Boolean值,通常的产品设计常见一种数据类型应该是对应一种视图类型,但是就是存在这么奇葩的设计,比如返回一个Person数据类型,如果sex为男需要展示一种样式(左右布局:头像在左边,右边显示简介),如果sex为女则需要展示另一种样式(上下布局:头像在中间,下面显示简介),这样的场景就可以这样定义两种视图:
    <ManCreator.kt>
    class ManCreator : ViewTypeCreator<Person, ManCreator.Holder>() {
        ...
        override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): Holder {
            return Holder(inflater.inflate(R.layout.view_type_man, parent, false))
        }
        // 当person为男性的时候会通过该Creator创建视图
        override fun match(data: Person) = data.sex == Sex.MAN
    }
    
    <WomanCreator.kt>
    class WomanCreator : ViewTypeCreator<Person, WomanCreator.Holder>() {
        ...
        override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): Holder {
            return Holder(inflater.inflate(R.layout.view_type_female, parent, false))
        }
        // 当person为女性的时候会通过该Creator创建视图
        override fun match(data: Person) = data.sex == Sex.WOMAN
    }
    
    1. 适配器,当然是继承RecyclerView.Adapter
    • 先介绍一下3个缓存:
    <MultiTypeAdapter.kt>
    abstract class MultiTypeAdapter : RecyclerView.Adapter<ViewHolder>() {
        // 按数据顺序,存储数据类型
        private val dataCache: ArrayList<Class<*>> = ArrayList()// [DataType]
        // 存储ViewTypeCreator,索引为该数据类型在缓存的下标,由于一个数据类型可对应多个Creator,
        // 因此使用集合存储
        private val creatorCache: SparseArray<SparseArray<ViewTypeCreator<Any, *>>> = SparseArray()// DataTypeIndex - [ViewTypeCreators]
        // 存储ViewTypeCreator,索引为对应的viewType,一对一的关系,该缓存是为了快速查找
        private val viewTypeCache: SparseArray<ViewTypeCreator<Any, *>> = SparseArray()// ViewType - ViewTypeCreator
    }
    
    • 接下来看下注册视图构建器的方法:
    <MultiTypeAdapter.kt>
        // 自动读取泛型Data数据的类型进行存储
        inline fun <reified T : Any> registerCreator(creator: ViewTypeCreator<T, *>) {
            registerCreatorInner(T::class.java, creator)
        }
    
        fun registerCreatorInner(clazz: Class<*>, creator: ViewTypeCreator<*, *>) {
            var index = dataCache.indexOf(clazz)
            if (index == -1) {
                // 如果没有存储过进行缓存
                dataCache.add(clazz)
                index = dataCache.size - 1
            }
            // 初始化该Data数据类型对应的ViewTypeCreator集合
            var cache = creatorCache[index]
            if (cache == null) {
                cache = SparseArray()
            }
            // 构造唯一标识作为索引
            val id = System.identityHashCode(creator)
            @Suppress("UNCHECKED_CAST")
            cache.put(id, creator as ViewTypeCreator<Any, *>)
            creatorCache.put(index, cache)
        }
    
    • 最后按照RecyclerView.Adapter调用流程分析下原理:
    <MultiTypeAdapter.kt>
        // 获取当前索引的数据
        abstract fun getData(position: Int): Any
    
        override fun getItemViewType(position: Int): Int {
            val data = getData(position)// 1.获取当前索引对应的数据
            val viewType = getCreatorViewType(data)// 2.根据当前数据获取ViewType
            return if (viewType != -1) {
                viewType
            } else
                super.getItemViewType(position)
        }
    
        private fun getCreatorViewType(data: Any): Int {
            val clazz: Class<*> = data::class.java// 获取data数据的class类型
            var viewType: Int
            val index = dataCache.indexOf(clazz)// 查找出该data在缓存中的索引
            if (dataCache.size > 0 && index != -1) {// 判断是否注册过该data对应的ViewTypeCreator
                val creators: SparseArray<ViewTypeCreator<Any, *>> = creatorCache[index]// 获取该data对应的ViewTypeCreator集合
                // The Data bind more than one viewTypeCreator.
                if (creators.size() > 1) {// 一个data数据对应多种viewType
                    creators.forEach { id, viewCreator ->
                        if (viewCreator.match(data)) {// 遍历ViewTypeCreator,并且判断是否符合条件,也就是上面说的match匹配方法
                            viewType = id// viewType就是上面根据ViewTypeCreator实例获取到的唯一id:System.identityHashCode
                            if (viewTypeCache.indexOfKey(viewType) < 0) {// 如果viewTypeCache没有缓存过,则添加入缓存
                                viewTypeCache.put(viewType, viewCreator)// 这一级缓存是为了能够快速根据viewType查找对应的ViewTypeCreator
                            }
                            return viewType
                        }
                    }
                }
                // The Data only bind one viewTypeCreator.
                else if (creators.size() == 1) {// 一个data数据对应一种viewType
                    viewType = creators.keyAt(0)
                    if (viewTypeCache.indexOfKey(viewType) < 0) {
                        viewTypeCache.put(viewType, creators.valueAt(0))
                    }
                    return viewType
                }
            }
            throw RuntimeException("Current dataType [$clazz] is not found in DataTypeCache:\n$dataCache \nPlease check the Type of data for your custom creator.")
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            // 根据viewType查找到对应的ViewTypeCreator,并且通过onCreateViewHolder构建Holer视图
            val viewCreator: ViewTypeCreator<*, *> = getViewCreatorByViewType(viewType)
            return viewCreator.onCreateViewHolder(LayoutInflater.from(parent.context), parent)
        }
    
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            // 根据viewType查找到对应的ViewTypeCreator,并且通过onBindViewHolder绑定数据
            val data = getData(position)
            @Suppress("UNCHECKED_CAST")
            val viewCreator: ViewTypeCreator<Any, ViewHolder> =
                getViewCreatorByViewType(getItemViewType(position)) as ViewTypeCreator<Any, ViewHolder>
            viewCreator.onBindViewHolder(holder, data)
        }
    
        // 根据viewType查找到对应的ViewTypeCreator
        private fun getViewCreatorByViewType(viewType: Int): ViewTypeCreator<Any, *> {
            return viewTypeCache[viewType]
        }
    

    以上就是MultiTypeAdapter的所有代码

    1. 下面举个使用案例:
    • 先定义一个适配器,继承MultiTypeAdapter
    class SampleAdapter : MultiTypeAdapter() {
    
        val data = mutableListOf<Any>()
    
        override fun getData(position: Int) = data[position]
    
        override fun getItemCount() = data.size
    }
    
    • 定义两种展示文字和一种展示图片的viewType
    // 文字数据类型定义:包含主标题和副标题
    data class Title(val mainTitle: String = "", val subTitle: String = "")
    // 文字样式1
    class MainTitleCreator : ViewTypeCreator<Title, MainTitleCreator.Holder>() {
        class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {
            val title: TextView = itemView.findViewById(R.id.main_title)
        }
    
        override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): Holder {
            return Holder(inflater.inflate(R.layout.view_type_main_title, parent, false))
        }
    
        override fun onBindViewHolder(holder: Holder, data: Title) {
            holder.title.text = data.mainTitle
        }
    
        override fun match(data: Title): Boolean {
            return !TextUtils.isEmpty(data.mainTitle) && TextUtils.isEmpty(data.subTitle)
        }
    }
    // 文字样式2
    class SubTitleCreator : ViewTypeCreator<Title, SubTitleCreator.Holder>() {
        class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {
            val title: TextView = itemView.findViewById(R.id.sub_title)
        }
    
        override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): Holder {
            return Holder(inflater.inflate(R.layout.view_type_sub_title, parent, false))
        }
    
        override fun onBindViewHolder(holder: Holder, data: Title) {
            holder.title.text = data.subTitle
        }
    
        override fun match(data: Title): Boolean {
            return !TextUtils.isEmpty(data.subTitle) && TextUtils.isEmpty(data.mainTitle)
        }
    }
    
    // 图片样式
    class ImageCreator : ViewTypeCreator<Int, ImageCreator.Holder>() {
        class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {
            val image: ImageView = itemView.findViewById(R.id.image)
        }
    
        override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): Holder {
            return Holder(inflater.inflate(R.layout.view_type_image, parent, false))
        }
    
        override fun onBindViewHolder(holder: Holder, data: Int) {
            holder.image.setImageResource(data)
        }
    
        override fun match(data: Int): Boolean {
            return false
        }
    }
    
    • 注册viewTypeCreator
    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            recycler_view.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
            val adapter = SampleAdapter()
            // image type
            adapter.registerCreator(ImageCreator())
            // the same bean but different view type
            adapter.registerCreator(MainTitleCreator())
            adapter.registerCreator(SubTitleCreator())
            for (i in 0..10) {
                adapter.data.add(R.drawable.test)
                adapter.data.add("I am string")
                adapter.data.add(Title("I am MainTitle"))
                adapter.data.add(Title("", "I am SubTitle"))
            }
            recycler_view.adapter = adapter
        }
    

    最终展示效果如下:

    device-2020-07-24-103721.png

    如果后续产品设计新增了样式,只需要定义新的ViewTypeCreator,再注册到MultiTypeAdapter中,然后在数据集中添加对应类型的数据即可,适配器和数据,视图构建完全解耦。

    可以直接远程依赖引入,项目地址:https://github.com/seagazer/multitype

    相关文章

      网友评论

        本文标题:如何用100行代码构建一个多样式RecyclerView适配器

        本文链接:https://www.haomeiwen.com/subject/cjqjlktx.html