Android app 优雅展示复杂列表

问题描述

Android 应用列表展示功能非常常见。特别是电商 新闻 这类的应用。一般主页就是一个复杂的列表。复杂列表一般包含多种展示样式,每一种样式或多种样式 对应一种数据结构,每种样式包含N个N>=0 item。

示例效果图

为了简化问题,每种颜色对应一种展示效果。

实现思路

经过不断的重构迭代,实现了一个非常优雅的 RecyclerView Adapter。可以展示各种复杂列表,而且代码简洁易懂,易扩展,代码高度复用。
列表中每一个样式的核心逻辑有两个,一个是样式对应的布局,也就是展示效果,另一个是布局中控件如何和数据关联。这两个功能对应接口ViewTypeDelegateAdapter 中 onCreateViewHolder和onBindViewHolder。

 interface ViewTypeDelegateAdapter {
        fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommonViewHolder
        fun onBindViewHolder(holder: CommonViewHolder, position: Int, data: Any?)
    }

总的实现思路是基于委托模式,每一种展示样式委托给一个轻量级的DelegateAdapter,实现展示效果。这个类非常简单,只需要实现接口中的两个函数。

核心功能由MultiTypeAdapter 实现,它负责根据每种展示样式委托给对应的代理。还有RecyclerView.Adapter
中的一些其他逻辑

为了实现委托定义了一个泛型数据结构

    data class CommonAdapterItem(val data: Any, val type: Int, var spanSize: Int = 1)

把要展示的数据使用CommonAdapterItem 包装一下,设置对应的展示样式和GridLayoutManager 对应的spanCount。然后把数据塞给 MultiTypeAdapter ,由它委托给对应的实现类。

核心代码和demo

代码已经在多个项目中使用过,经过多次优化已经非常的简单高效。为了方便查看,把所有的功能放在了一个类中,即使这样也不到200行代码

const val RED = 1
const val GREEN = 2
const val BLUE = 3
const val YELLOW = 4
const val PURPLE = 5


class MultiTypeAdapter : RecyclerView.Adapter() {

    interface ViewTypeDelegateAdapter {
        fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommonViewHolder
        fun onBindViewHolder(holder: CommonViewHolder, position: Int, data: Any?)
    }

    private val mContent: MutableList = mutableListOf()

    private val mFactory = DelegateAdapterFactory()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommonViewHolder {
        return mFactory.getDelegateAdapter(viewType).onCreateViewHolder(parent, viewType)
    }

    override fun onBindViewHolder(holder: CommonViewHolder, position: Int) {
        val type = mContent[position].type
        mFactory.getDelegateAdapter(type)
            .onBindViewHolder(holder, position, mContent[position].data)
    }

    override fun getItemViewType(position: Int): Int = mContent[position].type

    override fun getItemCount(): Int = mContent.size

    fun addItems(
        items: Collection?,
        type: Int = RED,
        append: Boolean = false,
        spanSize: Int = 1
    ) {
        items?.let {
            if (!append) {
                mContent.clear()
            }
            mContent.addAll(transform(items, type, spanSize))
            notifyDataSetChanged()
        }
    }

//    fun setOnItemClick(callback: (position: Int, data: Any?, action: Int, extra: Any?) -> Unit) {
//        mFactory.onItemClick = callback
//    }
//
//    fun setOnItemClick(callback: (position: Int, data: Any?, action: Int) -> Unit) {
//        mFactory.onItemClick = { position, data, action, _ ->
//            callback(position, data, action)
//        }
//    }

    fun getItemType(position: Int): Int = mContent[position].type

    fun clear() {
        mContent.clear()
        notifyDataSetChanged()
    }

    fun removeItem(position: Int) {
        mContent.removeAt(position)
        notifyItemRemoved(position)
    }

    fun changeItem(position: Int, item: Any?, type: Int, spanSize: Int = 1) {
        item?.let {
            if (position < mContent.size) {
                mContent[position] = transform(item, type, spanSize)
                notifyItemChanged(position)
            }
        }
    }

//    fun addItem(item: Collection?, type: Int = RED, append: Boolean = false) {
//        item?.let {
//            if (!append) {
//                mContent.clear()
//            }
//            mContent.add(CommonAdapterItem(item.toMutableList(), type))
//            notifyDataSetChanged()
//        }
//    }

    fun addItem(item: Any?, type: Int = RED, append: Boolean = false, spanSize: Int = 1) {
        item?.let {
            if (!append) {
                mContent.clear()
            }
            mContent.add(transform(item, type, spanSize))
            notifyDataSetChanged()
        }
    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        super.onAttachedToRecyclerView(recyclerView)
        val manager = recyclerView.layoutManager
        if (manager is GridLayoutManager) {
            manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
                override fun getSpanSize(position: Int): Int {
                    return mContent[position].spanSize
                }
            }
        }
    }

    private fun transform(item: Any, type: Int, spanSize: Int = 1): CommonAdapterItem {
        return CommonAdapterItem(item, type, spanSize)
    }

    private fun transform(
        items: Collection,
        type: Int,
        spanSize: Int = 1
    ): List {
        return items.map { CommonAdapterItem(it, type, spanSize) }
    }

    data class CommonAdapterItem(val data: Any, val type: Int, var spanSize: Int = 1)

    open class BaseDelegateAdapter(protected val layoutId: Int) : ViewTypeDelegateAdapter {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommonViewHolder {
            val view = LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
            return CommonViewHolder(view)
        }

        override fun onBindViewHolder(holder: CommonViewHolder, position: Int, data: Any?) {}
    }

    class DelegateAdapterFactory {
        private val adapterCache = SparseArray() //提高性能

        fun getDelegateAdapter(type: Int): ViewTypeDelegateAdapter {
            var adapter = adapterCache[type]
            if (adapter == null) {
                adapter = when (type) {
                    RED, GREEN, BLUE, YELLOW, PURPLE -> object :
                        BaseDelegateAdapter(R.layout.viewholder_example) {
                        override fun onBindViewHolder(
                            holder: CommonViewHolder,
                            position: Int,
                            data: Any?
                        ) {
                            super.onBindViewHolder(holder, position, data)
                            if(data is String){
                                holder.get(R.id.content).setBackgroundColor(Color.parseColor(data))
                            }
                        }
                    }
                    else -> {
                        BaseDelegateAdapter(android.R.layout.simple_list_item_1)
                    }
                }
                adapterCache.put(type, adapter)

            }
            return adapter
        }
    }
}

demo对应的示例代码

       val adapter = MultiTypeAdapter()
        recyclerView.layoutManager = GridLayoutManager(this,4)
        recyclerView.adapter = adapter
        adapter.addItem("#FF0000",RED, true,4)
        val greenList = MutableList(10){
            "#00FF00"
        }
        val blueList = MutableList(5){
            "#0000FF"
        }
        val yellowList = MutableList(5){
            "#FFFF00"
        }
        val purpleList = MutableList(5){
            "#AA66CC"
        }
        adapter.addItems(greenList, GREEN,true,1)
        adapter.addItems(blueList, BLUE,true,4)
        adapter.addItems(yellowList, YELLOW,true,2)
        adapter.addItems(purpleList, PURPLE, true,3)      

你可能感兴趣的:(Android app 优雅展示复杂列表)