RecyclerView3-面向接口优雅地实现多类型列表
2017-02-21 15:44
513 查看
前言
在开发中,不可避免的要面对一个列表中要实现多种布局类型的需求。看了这篇文章http://www.jianshu.com/p/1297d2e4d27a
获益良多,借此做出一个小demo,从设计的角度通过两种不同的方式实现一个简单的多类型列表,效果如下:
简单的多类型列表
本章节内容如下:
1.常规的多类型列表搭建方式
2.面向接口的多类型列表搭建方式
一.常规的多类型列表搭建方式
1.需求分析&设计思路
日常需求中,往往会有一些复杂的混合布局列表的需求,如上图所示,三种不同的布局,背景颜色分别为灰色,白色,黄色;每种布局中都有不同的效果或行为(比如案例中点击不同布局后弹出不同的Toast)。为了偷懒笔者将三种item的高度设置成了相同.实际需求可能会非常复杂,比如商品分类item+单个商品item,或者新闻类型item+单个新闻item,甚至是包含三级item列表的需求等等。
这种需求一般的思路是:
1.在Adapter内部的getItemViewType()方法中,根据数据源的bean的类型判断,返回不同的viewType值;
2.创建若干个不同的ViewHolder类,分别对应每种不同的Item;
3.在Adapter内部的onCreateViewHolder()方法中,根据viewType值创建不同的ViewHolder并返回;
4.在Adapter内部的onBindViewHolder()方法中,根据不同的holder,设定相对应的item中的控件属性以及事件监听。
2.代码展示
1.getItemViewType()
根据数据源判断item类型private final int ITEM_TYPE_1 = 1; private final int ITEM_TYPE_2 = 2; private final int ITEM_TYPE_3 = 3; @Override public int getItemViewType(int position) { //我们设定三种不同的item循环展示 if (datas.get(position) % 3 == 0) { return ITEM_TYPE_1; } else if (datas.get(position) % 3 == 1) { return ITEM_TYPE_2; } else { return ITEM_TYPE_3; } }
2.创建不同的ViewHolder
/** * item_type_1所对应的viewHolder--灰色 */ class MyViewHolder1 extends RecyclerView.ViewHolder { public MyViewHolder1(View itemView) { super(itemView); tv = (TextView) itemView.findViewById(R.id.tv_content1); } public TextView tv; } /** * item_type_2所对应的viewHolder--白色 */ class MyViewHolder2 extends RecyclerView.ViewHolder { public MyViewHolder2(View itemView) { super(itemView); tv = (TextView) itemView.findViewById(R.id.tv_content2); } public TextView tv; } /** * item_type_3所对应的viewHolder--黄色 */ class MyViewHolder3 extends RecyclerView.ViewHolder { public MyViewHolder3(View itemView) { super(itemView); tv = (TextView) itemView.findViewById(R.id.tv_content3); } public TextView tv; }
3.onCreateViewHolder()
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //这里的viewType值就是getItemViewType()中我们经过判断返回的条目类型viewType if(viewType== ITEM_TYPE_1){ //创建一条灰色的条目MyViewHolder1 return new MyViewHolder1(LayoutInflater.from(ctx).inflate(R.layout.listitem_type_1,parent,false)); }else if(viewType== ITEM_TYPE_2){ //白色的MyViewHolder2 return new MyViewHolder2(LayoutInflater.from(ctx).inflate(R.layout.listitem_type_2,parent,false)); }else { //黄色的MyViewHolder3 return new MyViewHolder3(LayoutInflater.from(ctx).inflate(R.layout.listitem_type_3,parent,false)); } }
4. onBindViewHolder()
根据需求,设定item中的控件属性以及事件监听。本案例中,简单设置为不同颜色的条目弹出不同的Toast。
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if(holder instanceof MyViewHolder1 ){ ((MyViewHolder1) holder).tv.setText("content"+position); ((MyViewHolder1) holder).tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(ctx,"MyViewHolder1",Toast.LENGTH_SHORT).show(); } }); }else if(holder instanceof MyViewHolder2){ ((MyViewHolder2) holder).tv.setText("content"+position); ((MyViewHolder2) holder).tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(ctx,"MyViewHolder2",Toast.LENGTH_SHORT).show(); } }); }else{ ((MyViewHolder3) holder).tv.setText("content"+position); ((MyViewHolder3) holder).tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(ctx,"MyViewHolder3",Toast.LENGTH_SHORT).show(); } }); } }
代码展示完毕,代码很好理解,相信注释得也很清楚了,实际效果和文章开头的gif图效果展示的一样,可以说完全到达了我们需求的效果。
这种实现的方式显然是非常简单的,但是我们不难从中发现一些问题:
3.常规搭建方式的弊端
1.类型检查与类型转型
在示例代码的onCreateViewHolder()和onBindViewHolder()方法中,我们可以看到非常多的篇幅用于if–else if–else的结构,对于3种列表的需求来说,这样的不同种类的item都要进行一次if判断的方式实现列表并不是非常优雅——如果说每次if判断new一个ViewHolder还可以接受,长达几十行的onBindViewHolder()方法对于大部分开发人员恐怕是难以忍受的(如果item布局和功能复杂的话,也许会有几百甚至更多行…)。2.不利于扩展
现在我们的项目有了一个新的需求,需要添加一个红色的item(也就是每四个item一循环),那么我们需要修改哪些代码呢?1.getItemViewType()类型判断。 需要(一个新的else if)
2.创建ViewHolder。需要
3.onCreateViewHolder()。 需要(new一个新的ViewHolder,包括LayoutInflater.inflate()方法)
4.onBindViewHolder()。 需要(一个新的else if)
我们可以看到,每当item改变或item类型增加,我们都要去改变adapter中很多的代码,扩展性非常差。
3.不利于维护
这点应该是上一点的延伸,随着列表中布局类型的修改或者功能变更,getItemViewType、onCreateViewHolder、onBindViewHolder中的代码都需要变更或增加,Adapter 中的代码会变得臃肿与混乱,增加了代码的维护成本。二.面向接口的多类型列表搭建方式
1.转机
从上文我们可以得知,我们想要避免的无外乎两点1.臃肿冗杂的if–else结构(类型判断)
2.牵一发动全身的adapter代码
首先看一下原本的代码,类似这样,if——else结构
private final int ITEM_TYPE_1 = 1; private final int ITEM_TYPE_2 = 2; private final int ITEM_TYPE_3 = 3; @Override public int getItemViewType(int position) { //我们设定三种不同的item循环展示 if (datas.get(position) % 3 == 0) { return ITEM_TYPE_1; } else if (datas.get(position) % 3 == 1) { return ITEM_TYPE_2; } else { return ITEM_TYPE_3; } }
我们改成这样 (源码地址:https://github.com/ButQingMei/Samples.git):
public interface ItemInterface { int type(ItemFactory factory); } public class ItemType1 implements ItemInterface { @Override public int type(ItemFactory factory) { return factory.type(this); } } public class ItemType2 implements ItemInterface { @Override public int type(ItemFactory factory) { return factory.type(this); } } public class ItemType3 implements ItemInterface { @Override public int type(ItemFactory factory) { return factory.type(this); } } public interface ItemFactory { int type(ItemType1 type1); int type(ItemType2 type2); int type(ItemType3 type3); } public class ItemFactoryList implements ItemFactory { private final int item_type_1= R.layout.listitem_type_1; private final int item_type_2= R.layout.listitem_type_2; private final int item_type_3= R.layout.listitem_type_3; @Override public int type(ItemType1 type1) { return item_type_1; //直接返回就是item所对应的布局文件 } @Override public int type(ItemType2 type2) { return item_type_2; } @Override public int type(ItemType3 type3) { return item_type_3; } }
添加了以上接口类和3个不同类型item的实现类,ItemFactory接口以及其实现类后,我们可以修改为:
private ArrayList<ItemInterface> datas2=new ArrayList<ItemInterface>(); @Override public int getItemViewType(int position) { return datas2.get(position).type(factory); //直接返回布局id作为viewType }
这里用到的factory在Adapter的构造器里进行初始化:
factory = new ItemFactoryList()
详情请看源码。
2.接口抽象
如上,通过ItemInterface接口,我们将三种不同的item实例化。在我们需要在getItemViewType(int position)方法中进行的if——else进行item的类型判断时,通过调用type()方法,只需要一行代码,就可以直接获得对应的viewType。
这样做好处如下:
1.在该方法中,返回的viewType值直接便是每种item所对应布局的资源ID,而不是简单的ViewType标识。
2.更利于拓展和维护,这几种布局的资源ID都集中在ItemFactoryList类中,当需要修改布局文件时,直接打开该类修改即可。
3.简化代码,Adapter中的代码显得更加简洁明了。
3.ViewHolder && onCreateViewHolder优化
在接下来的onCreateViewHolder()方法中,我们不难处理接下来的流程:根据返回的resID创建对应的ViewHolder,但是这里也需要增加if——else语句判断并new出不同的ViewHolder,导致整个方法非常臃肿,我们可以尝试创建一个ViewHolder的基类:public abstract class BaseAmazViewHolder<T> extends RecyclerView.ViewHolder { //放置view控件的容器,减少不必要的findViewById的操作 private SparseArray<View> views; private View mItemView; public BaseAmazViewHolder(View itemView) { super(itemView); views=new SparseArray<>(); this.mItemView=itemView; } //这个方法在对应的ViewHolder对象中调用,用于代替findViewById() public View getView(int resID){ View view=views.get(resID); if (view == null) { view = mItemView.findViewById(resID); views.put(resID,view); } return view; } //在对应的ViewHolder对象中根据需求实现该抽象方法,在onBindViewHolder()中调用 public abstract void setUpView(T model, int position, MyRecyclerViewAmazAdapter adapter); }
然后,分别实现不同种类的ViewHolder:
public class ItemHolder1 extends BaseAmazViewHolder { public ItemHolder1(View itemView) { super(itemView); } @Override public void setUpView(Object model, int position, final MyRecyclerViewAmazAdapter adapter) { ItemType1 type1 = (ItemType1) model; final TextView tv = (TextView) getView(R.id.tv_content1); tv.setText("content"+type1.values); tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(tv.getContext(),"ItemHolder1",Toast.LENGTH_SHORT).show(); } }); } }
然后在ItemFactoryList中添加代码:
public class ItemFactoryList implements ItemFactory { private final int item_type_1= R.layout.listitem_type_1; private final int item_type_2= R.layout.listitem_type_2; private final int item_type_3= R.layout.listitem_type_3; @Override public int type(ItemType1 type1) { return item_type_1; } @Override public int type(ItemType2 type2) { return item_type_2; } @Override public int type(ItemType3 type3) { return item_type_3; } //新增的代码 @Override public BaseAmazViewHolder createViewHolder(int type, View itemView) { if(item_type_1 == type){ return new ItemHolder1(itemView); }else if (item_type_2 == type){ return new ItemHolder2(itemView); }else if (item_type_3 == type){ return new ItemHolder3(itemView); } return null; } }
最后对onCreateViewHolder方法进行如下实现:
@Override public BaseAmazViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(ctx).inflate(viewType, parent, false); return factory.createViewHolder(viewType,view); }
从该方法中可以看到,根据多态,adapter直接create一个对应类型的布局View(可能是itemType1,也可能是2,3,4…),而创建什么样的ViewHolder这样的类型判断同样放到了factory中处理,这样无论是ViewType的判断,还是ViewHolder的判断,诸如此类的类型判断我们都交给同一个对象去封装处理,不仅adapter中代码简洁明了,而且更方便后期的维护。
4.onBindViewHolder && adapter总结展示
onBindViewHolder()的代码很简单,也很好理解:@Override public void onBindViewHolder(BaseViewHolder holder, int position) { holder.setUpView(models.get(position),position,this); }
可以看到,所有的控件处理和业务逻辑,事件监听等我们都可以在对应的ViewHolder的实体类中处理,同时也不再需要面对令人难以忍受数量的庞大代码块了。
优化基本结束,我们来看看Adapter的代码:
public class MyRecyclerViewAmazAdapter extends RecyclerView.Adapter<BaseAmazViewHolder> {
private Context ctx;
private ArrayList<Integer> datas;
private ArrayList<ItemInterface> datas2=new ArrayList<ItemInterface>();
private final ItemFactoryList factory;
public MyRecyclerViewAmazAdapter(Context ctx, ArrayList<Integer> datas){
this.ctx = ctx;
this.datas = datas;
factory = new ItemFactoryList();
initBeans();
}
//创建不同类型的对象用于展示成不同的布局item
private void initBeans() {
for(int i=0;i<datas.size();i++){
if(i%3==0){
datas2.add(new ItemType1(i));
}else if(i%3==1){
datas2.add(new ItemType2(i));
}else{
datas2.add(new ItemType3(i));
}
}
}
@Override
public int getItemViewType(int position) {
return datas2.get(position).type(factory);
}
@Override public BaseAmazViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(ctx).inflate(viewType, parent, false); return factory.createViewHolder(viewType,view); }
@Override
public void onBindViewHolder(BaseAmazViewHolder holder, int position) {
holder.setUpView(datas2.get(position),position,this);
}
@Override
public int getItemCount() {
return datas2.size();
}
}
Adapter的代码的确轻盈很多(总体代码变多,不过好处很明显,分工细致,便于维护)。
源码地址:https://github.com/ButQingMei/Samples.git
相关文章推荐
- [置顶] RecyclerView实现加载多种条目类型,仿新闻列表多种item布局.
- 【Android】 RecyclerView、ListView实现单选列表的优雅之路.
- 不一样的RecyclerView优雅实现复杂列表布局(二)
- RecyclerView、ListView实现单选列表的优雅之路
- 不一样的RecyclerView优雅实现复杂列表布局(一)
- Android RecyclerView、ListView实现单选列表的优雅之路.
- RecyclerView、ListView实现单选列表的优雅之路.
- RecyclerView、ListView实现单选列表的优雅之路
- 【Android】 RecyclerView、ListView实现单选列表的优雅之路.
- RecyclerView实现瀑布流遇到的各种问题(item移动,加载更多图片闪烁,以及定制各种类型Header和Footer)
- 解决列表 (ListView GrifView RecyclerView )结合CheckBox实现列表选择的的问题
- Android实现RecyclerView自定义列表,SwipeRefreshLayout实现下拉刷新
- RecyclerView实现倒序列表
- RecyclerView学习(四)----城市导航列表的实现(上)
- Android RecyclerView实现列表多选
- RecyclerView分组列表的实现及demo
- RecyclerView的初步使用(1)----代替ListView实现图片列表
- Android使用RecyclerView实现自定义列表、点击事件以及下拉刷新
- Android使用RecyclerView实现仿微信联系人列表
- 在滚动列表中实现视频的播放(ListView & RecyclerView)