使用MultiViewAdapter创建Recyclerview adapter[译]

原文:Create Android Recyclerview adapters like a boss with MultiViewAdapter 

1-43Kdmm5GaK-E_T29a1Csew.jpeg

RecyclerView是一个重要的控件,许多app都有使用。它是一个可以用在多种案例中的通用控件,但是以为其灵活性,也让adapter的创建多了许多工作。

支持多类型视图是其优于listview的一个方面。但是显示多类型视图需要许多公式化的重复代码。如果多余三个类型视图就有点让人手忙脚乱了。你可以多用几个if-else ,switch cases..但不幸的是,要重用床架和viewholder的代码不是一件易事。

MultiViewAdapter正是为了解决这个问题。虽然现在已经出现了几个解决方案,但是这些库都有一些限制:

  1. 对象需要继承一个父类,这可能跟你的数据模型有点冲突。

  2. 强制在model类中持有layout resource ID,这打乱了依赖关系。

  3. 无法自己管理view type ID,通常是将 resource ID 作为view type,所以你无法在两个不同的view type中使用同一布局文件。

  4. 它们都没有利用DiffUtil。

  5. 如果你想让不同的view type具有不同的item-decorations/span-size/selection-modes,你必须写switch case语句。

MultiViewAdapter解决了所有这些问题。这个库有意采用了不影响对象模型和关系的设计。

源码

屏幕快照 2017-06-29 17.39.30.png 

对于这个library所能达到的效果,让我们先睹为快:

ezgif-1-5e4ac19c36.gif

功能特色

  1. 对model的使用无限制。

  2. 对DiffUtil开箱即用的支持

  3. 支持单选和多选

  4. 每个view type可以有自己的span count 或者 ItemDecoration,你不必写switch cases 或者 if-else 语句。

如何使用

在app的gradle 文件中添加dependency

dependencies {
  // ... other dependencies here
    compile 'com.github.devahamed:multi-view-adapter:1.1.0'
}

背后的概念

1-xUtA2hYJeyOcHeqao_ExTA.jpeg

  1. cyclerAdapter —  adapter 类。它可以有多个ItemBinder和DataManager。继承自官方的RecyclerView.Adapter。

  2. ItemBinder —ItemBinder的职责是创建和绑定viewholder。它有一个type参数,接收需要显示的model类。ItemBinder需要在RecyclerAdapter中注册。

  3. DataManger — 它持有数据并且在数据修改的时候调用必要的动画。有两种DataManager。显示list的DataListManager以及显示一个item(比如Header, Footer 等)的DataItemManager。

创建简单的 adapter

1-g4yON409_UBDcl_0uK7lCQ.jpeg

假如你有一个对象列表,比如说“car”。如果你想显示一个“car”列表,下面是所有代码。

public class CarAdapter extends RecyclerAdapter {
  private DataListManager<CarModel> dataManager;
  public CarAdapter() {
    this.dataManager = new DataListManager<>(this);
    addDataManager(dataManager);
    registerBinder(new CarBinder());
  }
  public void addData(List<CarModel> dataList) {
    dataManager.addAll(dataList);
  }
}

CarAdapter.java hosted with ❤ by GitHub

class CarBinder extends ItemBinder<CarModel, CarBinder.CarViewHolder> {
  @Override public CarViewHolder create(LayoutInflater inflater, ViewGroup parent) {
    return new CarViewHolder(inflater.inflate(R.layout.item_car, parent, false));
  }
  @Override public boolean canBindData(Object item) {
    return item instanceof CarModel;
  }
  @Override public void bind(CarViewHolder holder, CarModel item) {
    // Bind the data here
  }
  static class CarViewHolder extends BaseViewHolder<ItemModel> {
    // Normal ViewHolder code
  }
}

接下来只需CarAdapter carAdapter = new CarAdapter(); 然后设置给recyclerview就可以了。

你可能注意到了,在传统方法种,我们只需一个CarAdapter类,但是使用这个库,你需要两个类- CarAdapter 和 CarBinder。这样做的目的是在其它adapter中使用CarBinder,比如VehicleAdapter。

使用 GridLayoutManager

1-sxgwzTrn7jBYdOuyVER3dg.jpeg

显示网格界面并不需要另外的adapter,你可以在ItemBinder类中重写getSpanSize(int maxSpanCount)方法,并返回span count。

class CarBinder extends ItemBinder<CarModel, CarBinder.CarViewHolder> {
  // Rest of the code
  
  @Override public int getSpanSize(int maxSpanCount) {
    return 1; // Return any number which is less than maxSpanCount 
  }
}

然后就可以从adapter中得到spansize look up 然后设置给你的GridLayoutManager。

其它span count

1-3Fi3p7ZYvvOQqkPt3_mrYA.jpeg

默认item binder 返回的span count为1,如果想要其它的span count,可以重写getSpanSize方法返回需要的span count。

自定义 Item Decoration

1-kX-yg-0eZKxyfA6oxQsjkg (1).jpeg

这是比较复杂的部分,如果不使用这个库的话会更复杂。为一个view type创建item decoration需要三个步骤。

1.创建一个继承了ItemDecorator的类

public class MyItemDecorator implements ItemDecorator {
  public MyItemDecorator() {
    // Any initialization code
  }
  @Override public void getItemOffsets(Rect outRect, int position, int positionType) {
    // Set item offset for each item
    // outRect.set(0, 0, 0, 0);
  }
  @Override public void onDraw(Canvas canvas, RecyclerView parent, View child, int position,
      int positionType) {
    // Canvas drawing code implementation
    // Unlike default ItemDecoration, this method will be called for individual items. Do not create objects here.
  }
}

2.创建ItemBinder的时候,把自定义的 item decorator 传递给构造函数。

public class CustomItemBinder implements ItemBinder {
  public CustomItemBinder(CustomItemDecorator customItemDecorator) {
    super(customItemDecorator);
  }
}

3. 然后就可以从adapter中得到item decoration ,并把它添加到recyclerview。

  // Inside activity / fragment
private void initRecyclerView() {
    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rcv);
    MyAdapter adapter = new MyAdapter();
    recyclerView.addItemDecoration(adapter.getItemDecorationManager());
    recyclerView.setAdapter(adapter);
}

哎,所谓的复杂就是这么多!

DiffUtil 以及Custom Payload

MultiViewAdapter默认处理好了DiffUtil。如果你想在diffutil操作期间传递payload,你需要通过构造器传递PayloadProvider对象。关于diffutil的详情请看 这里

class CarAdapter extends RecyclerAdapter {
  private DataListManager<CarModel> dataManager;
  private PayloadProvider<M> payloadProvider = new PayloadProvider<CarModel>() {
      @Override public boolean areContentsTheSame(CarModel oldItem, CarModel newItem) {
        // Your logic here
        return oldItem.equals(newItem);
      }
      @Override public Object getChangePayload(CarModel oldItem, CarModel newItem) {
        // Your logic here
        return null;
      }
    }
  public CarAdapter() {
    this.dataManager = new DataListManager<>(this, payloadProvider);
    addDataManager(dataManager);
    registerBinder(new CarBinder());
  }
  public void addData(List<CarModel> dataList) {
    dataManager.addAll(dataList);
  }
}

Making an adapter selectable

1-lS_UNTtEceK3qVWvprFdUw.jpeg

MultiViewAdapter支持三种类型的选择操作:

  1. 单选-只允许选择一个item,一旦一个item被选中,不能取消,除非另一个item被选中。

  2. 单选或者不选-只允许选中一个item。但是可以重新点击同一item取消选中。

  3. 多选-可以选择不同DataManager之间的多个item。

要让adapter可选择,需要使用“Selectable” 版本的Adapter, ItemBinder, 和 ViewHolder。比如,你可以使用SelectableAdapter, SelectableBinder 以及 SelectableViewHolder。

让我们以CarAdapter为例,让它可选择。

public class CarAdapter extends SelectableAdapter {
  private DataListManager<CarModel> dataManager;
  public CarAdapter() {
    setSelectionMode(SELECTION_MODE_SINGLE);
    
    this.dataManager = new DataListManager<>(this);
    addDataManager(dataManager);
    registerBinder(new CarBinder());
  }
  public void addData(List<CarModel> dataList) {
    dataManager.addAll(dataList);
  }
}

CarAdapter.java hosted with ❤ by GitHub

class CarBinder extends SelectableBinder<CarModel, CarBinder.CarViewHolder> {
  @Override public CarViewHolder create(LayoutInflater inflater, ViewGroup parent) {
    return new CarViewHolder(inflater.inflate(R.layout.item_car, parent, false));
  }
  @Override public boolean canBindData(Object item) {
    return item instanceof CarModel;
  }
  @Override public void bind(CarViewHolder holder, CarModel item, boolean isSelected) {
    // Bind the data here
    // Whenever the selection status changes, this ethod will be called.
    // Use "isSelected" to know whether the item is selectable
  }
  static class CarViewHolder extends SelectableViewHolder<CarModel> {
    
    CarViewHolder(View itemView) {
      super(itemView);
      setItemClickListener(new OnItemClickListener<CarModel>() {
        @Override public void onItemClick(View view, GridItem item) {
          itemSelectionToggled();
        }
      });
  }
}

注 :

  1. 并不是所有的ItemBinder都必须是可选的。比如,一个列表的header往往是不可选的,所以可以让HeaderBinder继承普通的ItemBinder。

  2. 你可以在普通的adapter中重用任何可选binder,但是并不起效果。要让一个item可选,adapter 和 binder 都必须继承相应的Selectable版本。

  3. 默认长按一个item可以实现选中,如果你想一个item被选中,在ViewHolder调用itemSelectionToggled()。

  4. 你可以使用 getSelectedItems() 和  setSelectedItems(List items) 从 DataListManager 中得到或者设置选中的item。

Listeners

ViewHolders有两个listener:OnItemClickListener 和 OnItemLongClickListener。

DataListManager有一个 ItemSelectionChangedListener 和 一个MultiSelectionChangedListener。这些listener可以和SelectableAdapter一起使用。

最后

感谢阅读!希望这篇文章可以帮助你开始使用MultiViewAdapter。该库现在还处于开发状态,我计划添加一些很酷的功能。你可以通过GitHub的watch操作接收通知,也可以在 GitHub上查看library的内容导航。

对于该库有任何疑问,可以通过Twitter联系我。