RecyclerView3-面向接口优雅地实现多类型列表

前言

在开发中,不可避免的要面对一个列表中要实现多种布局类型的需求。看了这篇文章

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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值