1、介绍
第一次见到MultiType时,真的被惊艳到了,没想到多类型列表可以写的这么简洁,解耦。之前有幸曾维护过一个有15种类型构成的ListView, 实在难以恭维,单个Adapter超过三千多行,各类型ViewHolder 插入其中。虽然ListView多类型Adapter 很好理解,重点就getTypeCount(),getViewType,getView()这三个方法, 但是这样写总感觉怪怪的, 如果项目组来个年轻人没用过ListView ,新增一种类型而忘了给 getTypeCount()方法加1,就要出线上事故,增加维护成本。

最近感悟到,在日常的业务开发中,觉得不爽,不合理的地方想想有没有更好的解决方式,多思考, 而不是一直在搬砖堆业务。 就MultiType而言,原理大家基本上看一下度能够理解,并不是很深奥的东西。 但是提出这个想法的人却比较少,并且在RecyclerVeiw 出现之后提出, 从Android 1.0时代,列表都是用ListView实现的, 大家实现多类型列表都是那三个方法, 为什么没有更简洁的方案提出来那(可能有些公司有大佬提出来没有开源),值得思考。
从去年开始一直想写关于ListView实现多类型列表的实现方式,一直拖到现在,一个原因是菜,一个原因是懒。之前工作太忙了。 趁着前段时间不太忙,把这个想法完善了一下。
了解MultiType原理的同学,理解接下来要说的其实问题不大, 不了解的同学其实可以看看我这篇, RecyclerView多类型列表实现—— MultiType分析, 简单的来说就是维护一个映射表,实现ViewType类型,Bean数据,ViewHolder的映射。
回忆一下MultiType的实现:
对应的映射表:
| ViewType | Java Bean Class | Linker | ItemViewBinder |
|---|---|---|---|
| 0 | C1 | L1 | binder_1 |
| 1 | C2 | L2 | binder_2_1 |
| 2 | C2 | L2 | binder_2_2 |
| 3 | C2 | L2 | binder_2_3 |
| 4 | C2 | L3 | binder_3_1 |
此次MultiTypeListViewAdapter的实现:
对应的映射表:
ViewType | Java Bean Class | ViewHolder ---|---|---|--- 0 | C0 | viewHolder0 1 | C1 | viewHolder1 2 | C2 | viewHolder2 3 | C3 | viewHolder3 4 | C4 | viewHolder4
可以看出此次实现的MultiTypeListViewAdapter相比MultiType只实现了一对一映射, 而没有实现一对多映射,如果有相关需求的话,可以考虑在数据层解决这个问题,即多个JavaBean绑定同一个ViewHolder。
如果你想对老项目中的多类型ListView进行整理或者小范围重构的话,可以试试这个方案, 对代码改动量和迁移量是比较小的。大范围重构可能就考虑RecyclerView了。
2、 使用
用法比较简洁, 和MultiType的用法其实是一样的
1、创建数据实体
data class TextItem(val text : String)
2、创建ViewHolder
class TextViewHolder : BaseViewHolder<TextItem>() {
private var text: TextView? = null
override fun getLayoutId(): Int {
return R.layout.item_text //返回VH的布局文件
}
override fun onBindViewHolder(rootView: View) {
text = rootView.findViewById(R.id.text) //绑定View
}
override fun render(item: TextItem, position: Int) {
text?.text = item.text //渲染数据和设置点击事件等
}
}
3、Adapter绑定ViewHolder,注入数据
class MainActivity : AppCompatActivity() {
private var mList: MutableList<Any>? = null
private var mListView: ListView? = null
private var adapter: MultiTypeListViewAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initData();
initAdapter();
mListView = findViewById(R.id.listview)
mListView?.adapter = adapter
}
private fun initData() {
mList = ArrayList()
for (i in 1..100) { //添加各种类型的数据
mList?.add(ImageItem(R.mipmap.ic_launcher))
mList?.add(RichItem("小艾大人赛高", R.drawable.ic_launcher_foreground))
mList?.add(TextItem("没有什么能够阻挡,你对自由的向往~"))
}
}
//注册ViewHolder
private fun initAdapter() { //绑定各个类型的VH和数据,填充映射表
adapter = MultiTypeListViewAdapter(this, mList)
adapter?.register(RichItem::class.java, RichTextViewHolder())
adapter?.register(ImageItem::class.java, ImageViewHolder())
adapter?.register(TextItem::class.java, TextViewHolder())
}
}
实现效果

3、原理
原理其实就是将getTypeCount(),getViewType,getView()这三个方法封装起来,帮助开发者避免这三个方法, 具体的实现方式就是维护这个映射表。
ViewType | Java Bean Class | ViewHolder ---|---|---|--- 0 | C0 | viewHolder0 1 | C1 | viewHolder1 2 | C2 | viewHolder2 3 | C3 | viewHolder3 4 | C4 | viewHolder4
怎么映射那,下面就开始上代码:
这个是项目结构,代码量很少, 大家如果感兴趣的话, 可以下载下来看看,不大只有四个类

1、TypePool
interface TypePool {
fun <T> register(clazz: Class<out T>, holder: BaseViewHolder<T>)
fun unregister(clazz: Class<*>)
fun size(): Int //类型数目
fun indexOf(clazz: Class<*>): Int //Class对应的positon
fun getBaseViewHolder(index: Int): BaseViewHolder<*>
}
2、MultiTypePool
public class MultiTypePool : TypePool {
private val classes: MutableList<Class<*>>
private val holders: MutableList<BaseViewHolder<*>>
constructor() { //维护上面所说的映射表
classes = ArrayList()
holders = ArrayList()
}
constructor(initialCapacity: Int) {
classes = ArrayList(initialCapacity)
holders = ArrayList(initialCapacity)
}
//注册和解绑其实都是对上述映射表的操作
override fun <T> register(
clazz: Class<out T>,
holder: BaseViewHolder<T>
) {
classes.add(clazz)
holders.add(holder)
}
override fun unregister(clazz: Class<*>) {
val postion = indexOf(clazz)
classes.remove(clazz)
holders.removeAt(postion)
}
override fun size(): Int { // 一共多少个类型的VH
return classes.size
}
override fun indexOf(clazz: Class<*>): Int { //对应的postion
return classes.indexOf(clazz)
}
override fun getBaseViewHolder(index: Int): BaseViewHolder<*> {
return holders[index]
}
}
3、BaseViewHolder
就是普通的ViewHolder, 约定俗成的实现以下方法, 可以看出并没有像MultiType在做一层代理,减少理解成本, 和平时的使用其实是一样的
public abstract class BaseViewHolder<T> {
protected var adapter: MultiTypeListViewAdapter? = null
// 获取布局id
abstract fun getLayoutId(): Int
abstract fun onBindViewHolder(rootView: View) // 绑定View
abstract fun render(item: T, position: Int) // 渲染数据
}
4、MultiTypeListViewAdapter
public class MultiTypeListViewAdapter extends BaseAdapter {
private static final String TAG = "MultiTypeListViewAdapter";
private List<?> items;
private TypePool typePool;
private LayoutInflater inflater;
// 下面这些方法,和ListView普通Adapter写法一样
public MultiTypeListViewAdapter(Context context) {
this(context, Collections.emptyList());
}
public MultiTypeListViewAdapter(Context context, List<?> items) {
this(context, items, new MultiTypePool());
}
public MultiTypeListViewAdapter(Context context, List<?> items, int initialCapacity) {
this(context, items, new MultiTypePool(initialCapacity));
}
public MultiTypeListViewAdapter(Context context, List<?> items, TypePool pool) {
this.items = items;
this.typePool = pool;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setItems(List<?> items) {
this.items = items;
}
public List<?> getItems() {
return items;
}
public <T> void register(Class<? extends T> clazz, BaseViewHolder<T> holder) {
typePool.register(clazz, holder);
holder.setAdapter(this); // 将Adapter注入VH中, 方便后面VH调用Adapter或者Activity的方法, 同时需要注意回收
}
@Override
public int getCount() {
return items.size();
}
@Override
public Object getItem(int position) {
return items.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
BaseViewHolder viewHolder;
int viewType = getItemViewType(position);
// if (convertView == null) {
// viewHolder = typePool.getBaseViewHolder(viewType);
// convertView = inflater.inflate(viewHolder.getLayoutId(), parent, false);
// viewHolder.onBindViewHolder(convertView);
//
// convertView.setTag(viewHolder);
// } else {
// viewHolder = typePool.getBaseViewHolder(viewType);
// }
// 这块的VH 复用还是有点问题, 写出来还是想找大家看看有没有好的方式解决这个
// 下面这块是VH不复用的方式
viewHolder = typePool.getBaseViewHolder(viewType); //拿到相应的ViewHolder
convertView = inflater.inflate(viewHolder.getLayoutId(), parent, false); //初始化布局
viewHolder.onBindViewHolder(convertView); //ViewHolder绑定布局
viewHolder.render(getItem(position), position); //ViewHolder渲染数据
return convertView;
}
@Override
public int getViewTypeCount() { // 就是返回映射表的行数量
return typePool.size();
}
@Override
public int getItemViewType(int position) { // 返回该用第几行的类型
return typePool.indexOf(items.get(position).getClass());
}
}
目前这个getView()方法中的VH是没有复用的, 不推荐这么做,但是复用版写的代码复用的有点问题,想让大家看看改怎么修改,这也是开源嘛, 逃(为自己的菜找原因)。
4、总结
大家日常写列表可能已经不考虑ListView了,都使用RecyclerView了。虽然这篇文章讲的是ListView实现的多类型列表,也是近期来的一些思考,分享出来。感兴趣的同学可以下载源码看看。
GitHub地址:github.com/stevenwsg/M…