MultiType 源码分析

MultiType 源码分析


前言

空闲时间不学习点什么真的是心慌慌的。这次就来分析分析 MultiType 这个三方库。挺不错的一个东西,也是我在好几个项目里用到的三方库。希望这个文章能够帮助到其他人吧,虽然希望渺茫~


MultiType 简介

MultiType 是对 RecyclerView 的 Adapter 的一个扩展,它把 面向 Adapter 编程变成了面向 ViewBinder 编程。之前的对于不同类型的 item 的处理,需要自己在 Adapter 中去手动区分手动处理,这样写出来的 Adapter 代码逻辑复杂不好维护,所有的 item 类型的处理都是在一块的;而 MultiType 对每种类型的 item,都有一个单独的 ViewBinder 去处理各自 item 的 ui 和代码逻辑,MultiType 通过继承并重写 RecyclerView 的 Adapter,把原本 Adapter 里边的对 item 的 ui 和 逻辑的处理,传递到了相应的 ViewBinder 中去处理,这样就把不同类型 item 的逻辑都单独封装在了 ViewBinder 里边。

这样处理的好处也是很明显的,它把不同类型的 item 都模块化了,便于不同类型 item 的修改替换组合。


MultiType 简单使用

  1. 在 gradle 文件里引用

    implementation 'me.drakeet.multitype:multitype:3.5.0'

  2. 创建一个数据模型

    public class TextItem {
    
        public final @NonNull String text;
    
        public TextItem(@NonNull String text) {
            this.text = text;
        }
    }
    
    
  3. 创建一个类(ViewBinder)继承自 ItemViewBinder

    
    public class TextItemViewBinder extends ItemViewBinder {
    
        static class TextHolder extends RecyclerView.ViewHolder {
        
            private @NonNull final TextView text;
    
            TextHolder(@NonNull View itemView) {
                super(itemView);
                this.text = (TextView) itemView.findViewById(R.id.text);
            }
        }
    
        @NonNull @Override
        protected TextHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
            View root = inflater.inflate(R.layout.item_text, parent, false);
            return new TextHolder(root);
        }
    
        @Override
        protected void onBindViewHolder(@NonNull TextHolder holder, @NonNull TextItem textItem) {
            holder.text.setText("hello: " + textItem.text);
            Log.d("demo", "position: " + getPosition(holder));
            Log.d("demo", "adapter: " + getAdapter());
        }
    }
    
    
  4. 创建 MultiTypeAdapter 并注册 ViewBinder

    
    public class SampleActivity extends AppCompatActivity {
    
        private MultiTypeAdapter adapter;
        private Items items;
    
        @Override 
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
    
            adapter = new MultiTypeAdapter();
            adapter.register(TextItem.class, new TextItemViewBinder());
            adapter.register(ImageItem.class, new ImageItemViewBinder());
            adapter.register(RichItem.class, new RichItemViewBinder());
            recyclerView.setAdapter(adapter);
    
            TextItem textItem = new TextItem("world");
            ImageItem imageItem = new ImageItem(R.mipmap.ic_launcher);
            RichItem richItem = new RichItem("小艾大人赛高", R.mipmap.avatar);
    
            items = new Items();
            for (int i = 0; i < 20; i++) {
                items.add(textItem);
                items.add(imageItem);
                items.add(richItem);
            }
            adapter.setItems(items);
            adapter.notifyDataSetChanged();
        }
    }
    
    

MultiType 源码分析

MultiType 的工作流程分析(一对一)

我们一般情况下对 RecyclerView 的 item 的处理都是在 Adapter 里边处理的,但是 MultiType 却是在 ViewBinder 里边处理的,它是怎么把逻辑转换到 Viewbinder 里的?看了上边的简单使用的介绍,它实例化了 MultiTypeAdapter 这个适配器并且设置给了 RecyclerView,这个操作是常规操作,但是 item 逻辑就是到 ViewBinder 里边去了;再看这个 adapter,它提供了注册 ViewBinder 的方法,看上去这个 MultiTypeAdapter 好像并不简单,那就从它开始突破吧。。

看它的构造方法:


    public class MultiTypeAdapter extends RecyclerView.Adapter {

          private static final String TAG = "MultiTypeAdapter";
        
          private @NonNull List items;
          private @NonNull TypePool typePool;
        
        
          /**
           * Constructs a MultiTypeAdapter with an empty items list.
           */
          public MultiTypeAdapter() {
            this(Collections.emptyList());
          }
        
        
          /**
           * Constructs a MultiTypeAdapter with a items list.
           *
           * @param items the items list
           */
          public MultiTypeAdapter(@NonNull List items) {
            this(items, new MultiTypePool());
          }
        
        
          /**
           * Constructs a MultiTypeAdapter with a items list and an initial capacity of TypePool.
           *
           * @param items the items list
           * @param initialCapacity the initial capacity of TypePool
           */
          public MultiTypeAdapter(@NonNull List items, int initialCapacity) {
            this(items, new MultiTypePool(initialCapacity));
          }
        
        
          /**
           * Constructs a MultiTypeAdapter with a items list and a TypePool.
           *
           * @param items the items list
           * @param pool the type pool
           */
          public MultiTypeAdapter(@NonNull List items, @NonNull TypePool pool) {
            checkNotNull(items);
            checkNotNull(pool);
            this.items = items;
            this.typePool = pool;
          }
          ...
    }

可以看到,这几个构造方法最终都会调用 MultiTypeAdapter(@NonNull List items, @NonNull TypePool pool) 这个构造方法。这里以调用无参构造方法为例,当最终调用到此构造方法的时候,先判断参数合法性,然后 保存 数据集合 items 和 MultiTypePool 的实例

接下来再来看一下 MultiTypeAdapter#register(@NonNull Class clazz, @NonNull ItemViewBinder binder) 方法:


  public  void register(@NonNull Class clazz, @NonNull ItemViewBinder binder) {
    checkNotNull(clazz);
    checkNotNull(binder);
    checkAndRemoveAllTypesIfNeeded(clazz);
    register(clazz, binder, new DefaultLinker());
  }


   void register(
      @NonNull Class clazz,
      @NonNull ItemViewBinder binder,
      @NonNull Linker linker) {
    typePool.register(clazz, binder, linker);
    binder.adapter = this;
  }

这个方法里,主要是调用 MultiTypePool 的 register 方法去注册 viewBinder,并且把 adapter 保存到 ViewBinder 里。

这里还有个需要注意的地方,checkAndRemoveAllTypesIfNeeded(clazz); 这个方法,会调用 MultiTypePool 的 unregister 方法,如果取消注册成功了,说明之前的实体类已经绑定过 ViewBinder 了,如果再注册这个实体类的话,之前的绑定会被删除,然后绑定新的 ViewBinder。

这里先简单介绍一下 MultiTypePool,他是一个类型池,里边维护了三个数组,分别保存数据模型的类型、与数据模型绑定的 Viewbinder、链接前边两者的一个数据类型对象。ViewBinder 的注册和取消注册其实就是对 MultiTypePool 里这三个数组的添加删除操作。

我们都知道,对一个常规的 RecyclerView 的 Adapter 的处理流程:实现 onCreateViewHolder、onBindViewHolder、getItemViewType、getItemCount。接下来看看 MultiTypeAdapter 是怎么处理这几个方法的:

  
  @Override
  public final int getItemViewType(int position) {
    Object item = items.get(position);
    return indexInTypesOf(position, item);
  }
  
  int indexInTypesOf(int position, @NonNull Object item) throws BinderNotFoundException {
    int index = typePool.firstIndexOf(item.getClass());
    if (index != -1) {
      @SuppressWarnings("unchecked")
      Linker linker = (Linker) typePool.getLinker(index);
      return index + linker.index(position, item);
    }
    throw new BinderNotFoundException(item.getClass());
  }
  
  @Override
  public final ViewHolder onCreateViewHolder(ViewGroup parent, int indexViewType) {
    LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    ItemViewBinder binder = typePool.getItemViewBinder(indexViewType);
    return binder.onCreateViewHolder(inflater, parent);
  }
  
  @Override @Deprecated
  public final void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    onBindViewHolder(holder, position, Collections.emptyList());
  }

  @Override @SuppressWarnings("unchecked")
  public final void onBindViewHolder(ViewHolder holder, int position, @NonNull List payloads) {
    Object item = items.get(position);
    ItemViewBinder binder = typePool.getItemViewBinder(holder.getItemViewType());
    binder.onBindViewHolder(holder, item, payloads);
  }
  
  @Override
  public final int getItemCount() {
    return items.size();
  }

 
  

先来看一下 getItemViewType 这个方法的处理,它是设置 item 对应的类型标志的,它调用了 indexInTypesOf 方法,在这个方法里,首先获取数据模型类型在对应集合中的第一个索引;然后再从 Linker 中获取到数据模型类型与 ViewBinder 的代表对应关系的数字;然后返回对应关系数字和索引的和,这两个数字的和就表示这 item 的类型标志。这里边有一点需要注意,在一对一对应关系中, Linker 的默认实现是 DefaultLinkerDefaultLinker#index 方法始终返回 0,所以可以看出 getItemViewType 返回的 item 类型标志其实就是 item 的数据类型在 MultiTypePool 里的数据集合里的索引值。

再看 onCreateViewHolder、onBindViewHolder 方法,这两个本来处理 recyclerView 的 item 的 ui 和逻辑的地方,在这里直接调用了对应的不同类型 item 的 ViewBinder 里边相应的方法,很巧妙的把不同类型的 item 的处理分隔开了。注意 ViewBinder 的 onBindViewHolder 方法,支持了 payloads 参数,意味着它也提供了对局部刷新的支持。getItemCount 返回数据集合的 size,这个没有啥变化。

由于原来要在 Adapter 中处理的逻辑现在都要放在 ViewBinder 里边处理了,MultiType 也使 ViewBinder 支持了一些 Adapter 里的方法:


  @Override @SuppressWarnings("unchecked")
  public final void onViewRecycled(@NonNull ViewHolder holder) {
    getRawBinderByViewHolder(holder).onViewRecycled(holder);
  }
  
  @Override @SuppressWarnings("unchecked")
  public final boolean onFailedToRecycleView(@NonNull ViewHolder holder) {
    return getRawBinderByViewHolder(holder).onFailedToRecycleView(holder);
  }  
  
  @Override @SuppressWarnings("unchecked")
  public final void onViewAttachedToWindow(@NonNull ViewHolder holder) {
    getRawBinderByViewHolder(holder).onViewAttachedToWindow(holder);
  }
  
  @Override @SuppressWarnings("unchecked")
  public final void onViewDetachedFromWindow(@NonNull ViewHolder holder) {
    getRawBinderByViewHolder(holder).onViewDetachedFromWindow(holder);
  }

到这里 MultiType 这个 RecyclerView 的潘多拉魔盒也打开的差不多了,这里来个小总结加深一下印象:ViewBinder 通过 MultiType 内部的 MultiTypeAdapter 的 register 方法注册到类型池 MultiTypePool 中;MultiType 内部的 MultiTypeAdapter 在 onCreateViewHolder、onBindViewHolder 方法里直接从类型池中获取到相应的 ViewBinder 并调用相应的方法,把 item 逻辑转移到 ViewBinder 中。

TypePool 的作用

它是一个接口,定义了类型池的一些必要的方法:


public interface TypePool {


   void register(
      @NonNull Class clazz,
      @NonNull ItemViewBinder binder,
      @NonNull Linker linker);

  boolean unregister(@NonNull Class clazz);

  int size();

  int firstIndexOf(@NonNull Class clazz);

  @NonNull Class getClass(int index);

  @NonNull ItemViewBinder getItemViewBinder(int index);

  @NonNull Linker getLinker(int index);
}

它的实现类是 MultiTypePool,下边分析一下 MultiTypePool 这个类:

先看它的构造方法:

  private final @NonNull List> classes;
  private final @NonNull List> binders;
  private final @NonNull List> linkers;

  public MultiTypePool() {
    this.classes = new ArrayList<>();
    this.binders = new ArrayList<>();
    this.linkers = new ArrayList<>();
  }
  
  public MultiTypePool(int initialCapacity) {
    this.classes = new ArrayList<>(initialCapacity);
    this.binders = new ArrayList<>(initialCapacity);
    this.linkers = new ArrayList<>(initialCapacity);
  }
  
  public MultiTypePool(
      @NonNull List> classes,
      @NonNull List> binders,
      @NonNull List> linkers) {
    checkNotNull(classes);
    checkNotNull(binders);
    checkNotNull(linkers);
    this.classes = classes;
    this.binders = binders;
    this.linkers = linkers;
  }

构造方法里主要是初始化 classes、binders、linkers 这三个集合。

接着看它的 register 方法,前边分析的时候,当 adapter 调用 register 方法去注册 ViewBinder 的时候,最终调用的就是 MultiTypePool 的 register 方法:


  @Override
  public  void register(
      @NonNull Class clazz,
      @NonNull ItemViewBinder binder,
      @NonNull Linker linker) {
    checkNotNull(clazz);
    checkNotNull(binder);
    checkNotNull(linker);
    classes.add(clazz);
    binders.add(binder);
    linkers.add(linker);
  }

它做的事情很简单,检查参数合法性后,把数据放入相应的集合里。

unregister 方法就是把相应的数据从集合里移除;size 方法返回数据模型类型集合的大小;getClass、getItemViewBinder、getLinker 方法是根据索引获取三个集合里对应的数据。

下边看一下 firstIndexOf 方法:


  @Override
  public int firstIndexOf(@NonNull final Class clazz) {
    checkNotNull(clazz);
    //拿到数据模型类型在集合中的索引值
    int index = classes.indexOf(clazz);
    if (index != -1) {
      return index;
    }
    //如果数据模型类型在集合中不存在,就找集合中是否存在它的父类,或它实现的接口
    for (int i = 0; i < classes.size(); i++) {
      if (classes.get(i).isAssignableFrom(clazz)) {
        return i;
      }
    }
    return -1;
  }

有一点需要注意,classes.indexOf(clazz) 获取的是集合中第一个 clazz 的索引值,如果有相同的 clazz,这里返回的也是第一个 clazz 的索引值。

在获取索引的时候,如果集合中有它的父类或者它实现的接口,也是可以获取到索引值的,这一点比较奇特。

到此 MultiTypePool 这个类就分析完了,它的主要作用就是对注册的 ViewBinder 保存记录起来,对取消注册的 ViewBinder 从记录里边把它移除。

MultiType 一对多的实现

先看一下 MultiType 一个数据模型对应多个 ViewBinder 的注册时的写法:


    //方式1:实现 Linker 接口,根据数据模型里的类型字段返回相应的 ViewBinder 索引值
    //,这个索引值与前边的 to 方法传入 ViewBinder 的位置对应
    adapter.register(Data.class).to(
        new DataType1ViewBinder(),
        new DataType2ViewBinder()
    ).withLinker((position, data) ->
        data.type == Data.TYPE_2 ? 1 : 0
    );

    //方式2:实现 ClassLinker 接口,根据数据模型里的类型字段返回相应的 ViewBinder 的 class 对象
    adapter.register(Data.class).to(
        new DataType1ViewBinder(),
        new DataType2ViewBinder()
    ).withClassLinker((position, data) -> {
      if (data.type == Data.TYPE_2) {
        return DataType2ViewBinder.class;
      } else {
        return DataType1ViewBinder.class;
      }
    });

看了上边的一对多 ViewBinder 是不是有点懵,没事咱接着慢慢分析,先看看 MultiTypeAdapter 的 register(@NonNull Class clazz):


  @CheckResult
  public @NonNull  OneToManyFlow register(@NonNull Class clazz) {
    checkNotNull(clazz);
    checkAndRemoveAllTypesIfNeeded(clazz);
    return new OneToManyBuilder<>(this, clazz);
  }

这里直接返回了 OneToManyBuilder 这个类的实例,那么接下来就看看 OneToManyBuilder 是个啥:

构造方法:


  OneToManyBuilder(@NonNull MultiTypeAdapter adapter, @NonNull Class clazz) {
    this.clazz = clazz;
    this.adapter = adapter;
  }

保存了数据模型类型的实例和 MultiTypeAdapter 的实例。

上边一对多注册的时候,调用了 OneToManyBuilder#to(@NonNull ItemViewBinder... binders):


  @Override @CheckResult @SafeVarargs
  public final @NonNull OneToManyEndpoint to(@NonNull ItemViewBinder... binders) {
    checkNotNull(binders);
    this.binders = binders;
    return this;
  }

把一对多的这个 ViewBinder 的集合保存到 OneToManyBuilder 中。to 方法返回的是 OneToManyEndpoint 这个接口,这个接口里只有两个方法: withLinker、withClassLinker,看它在 OneToManyBuilder 中的实现:


  @Override
  public void withLinker(@NonNull Linker linker) {
    checkNotNull(linker);
    doRegister(linker);
  }

  @Override
  public void withClassLinker(@NonNull ClassLinker classLinker) {
    checkNotNull(classLinker);
    doRegister(ClassLinkerWrapper.wrap(classLinker, binders));
  }

  private void doRegister(@NonNull Linker linker) {
    for (ItemViewBinder binder : binders) {
      adapter.register(clazz, binder, linker);
    }
  }

上边方法最终调用了 doRegister 把 ViewBinder 通过 adapter 的 register 方法注册到类型池。需要注意的是,这个三参数的 register 方法,没有调用 checkAndRemoveAllTypesIfNeeded(clazz);,所以它在注册的时候,不会删除之前的已注册,这样就实现了一对多。

有一些细节地方还是要提一下的,在分析一对一流程的时候有个调用链 MultiTypeAdapter#getItemViewType(int position) -> MultiTypeAdapter#indexInTypesOf(int position, @NonNull Object item),这里是设置对应位置 item 的类型标志的,最终返回的值是 数据模型类型在集合中的第一个索引值 + Linker#index 返回的值,这个处理真的是很巧妙了,要实现一对多,只是用数据模型类型在集合中的第一个索引值肯定是实现不了的,所以 Linker 就派上用场了,自己实现 Linker 的 index 方法,在设置 item 类型的方法中,就能准确的返回当前的索引值,即类型标志了。

自己实现 Linker 的 index 方法时,应该怎么返回这个值?从 OneToManyBuilder#doRegister 这个方法的实现可以看出,当注册一对多时,对应的这多个 ViewBinder 是通过循环遍历 OneToManyBuilder 中的 binders 集合去注册的,所以注册的顺序和 binders 集合保存的顺序相同,所以在实现 index 方法时,直接返回 ViewBinder 对应的索引就行了,当调用 getItemViewType 方法时,刚好返回的是(类型池中第一个这个类型的索引 + OneToManyBuilder 中 binders 需要使用的 ViewBinder 对应的索引),这就保证了一对多时 item 类型是不同的了。

上边介绍了一对多注册的两种方式,withLinker 这个应该已经清楚了,再看看第二个写法 withClassLinker。它不同于方式一的就是,在 index 方法返回的不是索引值,而是 ViewBinder 的 class 实例,那方式二是怎么得到正确的索引值的呢?再回到OneToManyBuilder#withClassLinker(@NonNull ClassLinker classLinker) 方法中,发现它在调用 doRegister 方法时,传入的参数是一个 ClassLinkerWrapper 的实例,它的实现很简单,它实现了 Linker 接口,在 index 方法里通过 ClassLinker#index 方法获取到对应的 ViewBinder,然后从 ClassLinkerWrapper 的 binders 集合里获取到对应的索引值返回,

Linker 的作用

其实从上边一路分析下来,Linker 的作用已经交代的非常清楚了,它是为了实现一对多而设计的,由于一对多时,类型池里边的集合数据会有重复地出现,这时候获取索引值就可能会不准确了(只能获取到相同数据的第一个索引值),这时候再通过 Linker 去设置相同数据模型类型对应的不同 ViewBinder 的索引值,两个索引值相加就获取到了正确的值。


总结

林林总总的也算是分析完了,自我感觉收获挺多,当然在这个过程中也能看出自己的不足。比如文字描述能力。。其实自我感觉写的是有点混乱的,文字表达能力有点差,这方面还是需要再加强一下。。。

你可能感兴趣的:(android)