您的位置:首页 > 其它

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: