使用MultiViewAdapter创建Recyclerview adapter[译]
原文: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。
创建简单的 adapter
假如你有一个对象列表,比如说“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
显示网格界面并不需要另外的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
默认item binder 返回的span count为1,如果想要其它的span count,可以重写getSpanSize方法返回需要的span count。
自定义 Item Decoration
这是比较复杂的部分,如果不使用这个库的话会更复杂。为一个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
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 items) 从 DataListManager 中得到或者设置选中的item。
Listeners
ViewHolders有两个listener:OnItemClickListener 和 OnItemLongClickListener。
DataListManager有一个 ItemSelectionChangedListener 和 一个MultiSelectionChangedListener。这些listener可以和SelectableAdapter一起使用。
最后
感谢阅读!希望这篇文章可以帮助你开始使用MultiViewAdapter。该库现在还处于开发状态,我计划添加一些很酷的功能。你可以通过GitHub的watch操作接收通知,也可以在 GitHub上查看library的内容导航。
对于该库有任何疑问,可以通过Twitter联系我。