原文:Create Android Recyclerview adapters like a boss with MultiViewAdapter
RecyclerView是一个重要的控件,许多app都有使用。它是一个可以用在多种案例中的通用控件,但是以为其灵活性,也让adapter的创建多了许多工作。
支持多类型视图是其优于listview的一个方面。但是显示多类型视图需要许多公式化的重复代码。如果多余三个类型视图就有点让人手忙脚乱了。你可以多用几个if-else ,switch cases..但不幸的是,要重用床架和viewholder的代码不是一件易事。
MultiViewAdapter正是为了解决这个问题。虽然现在已经出现了几个解决方案,但是这些库都有一些限制:
对象需要继承一个父类,这可能跟你的数据模型有点冲突。
强制在model类中持有layout resource ID,这打乱了依赖关系。
无法自己管理view type ID,通常是将 resource ID 作为view type,所以你无法在两个不同的view type中使用同一布局文件。
它们都没有利用DiffUtil。
如果你想让不同的view type具有不同的item-decorations/span-size/selection-modes,你必须写switch case语句。
MultiViewAdapter解决了所有这些问题。这个库有意采用了不影响对象模型和关系的设计。
对于这个library所能达到的效果,让我们先睹为快:
对model的使用无限制。
对DiffUtil开箱即用的支持
支持单选和多选
每个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' }
cyclerAdapter — adapter 类。它可以有多个ItemBinder和DataManager。继承自官方的RecyclerView.Adapter。
ItemBinder —ItemBinder的职责是创建和绑定viewholder。它有一个type参数,接收需要显示的model类。ItemBinder需要在RecyclerAdapter中注册。
DataManger — 它持有数据并且在数据修改的时候调用必要的动画。有两种DataManager。显示list的DataListManager以及显示一个item(比如Header, Footer 等)的DataItemManager。
假如你有一个对象列表,比如说“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。
显示网格界面并不需要另外的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。
默认item binder 返回的span count为1,如果想要其它的span count,可以重写getSpanSize方法返回需要的span count。
这是比较复杂的部分,如果不使用这个库的话会更复杂。为一个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); }
哎,所谓的复杂就是这么多!
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); } }
MultiViewAdapter支持三种类型的选择操作:
单选-只允许选择一个item,一旦一个item被选中,不能取消,除非另一个item被选中。
单选或者不选-只允许选中一个item。但是可以重新点击同一item取消选中。
多选-可以选择不同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(); } }); } }
注 :
并不是所有的ItemBinder都必须是可选的。比如,一个列表的header往往是不可选的,所以可以让HeaderBinder继承普通的ItemBinder。
你可以在普通的adapter中重用任何可选binder,但是并不起效果。要让一个item可选,adapter 和 binder 都必须继承相应的Selectable版本。
默认长按一个item可以实现选中,如果你想一个item被选中,在ViewHolder调用itemSelectionToggled()。
你可以使用 getSelectedItems() 和 setSelectedItems(List<E> items) 从 DataListManager 中得到或者设置选中的item。
ViewHolders有两个listener:OnItemClickListener 和 OnItemLongClickListener。
DataListManager有一个 ItemSelectionChangedListener 和 一个MultiSelectionChangedListener。这些listener可以和SelectableAdapter一起使用。
感谢阅读!希望这篇文章可以帮助你开始使用MultiViewAdapter。该库现在还处于开发状态,我计划添加一些很酷的功能。你可以通过GitHub的watch操作接收通知,也可以在 GitHub上查看library的内容导航。
对于该库有任何疑问,可以通过Twitter联系我。