问题描述
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<CommonViewHolder>() {
interface ViewTypeDelegateAdapter {
fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommonViewHolder
fun onBindViewHolder(holder: CommonViewHolder, position: Int, data: Any?)
}
private val mContent: MutableList<CommonAdapterItem> = 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<Any>?,
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<Any>?, 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<Any>,
type: Int,
spanSize: Int = 1
): List<CommonAdapterItem> {
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<ViewTypeDelegateAdapter>() //提高性能
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<View>(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)