今天看见一篇文章,讲到了RecyclerView一些关于加载多种布局样式时通常用法存在的一些问题,下面是文章地址:http://www.jianshu.com/p/c6a44e18badb 这里先看一下通常大多数人的用法,如下:
package com.jason.recycleview.lylrecycleviewadpdemo;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
public class MyAdpter extends RecyclerView.Adapter {
private int type1 = 1;
private int type2 = 2;
private Context mContext;
private List<Data> mList;
public MyAdpter(Context con, List<Data> list) {
this.mContext = con;
this.mList = list;
}
@Override
public int getItemViewType(int position) {
if (position == 0)
return type1;
if (position == 1)
return type2;
return super.getItemViewType(position);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == type1) {
return new ViewHolder1();
} else if (viewType == type2) {
return new ViewHolder2();
}
return new ViewHolder3();
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ViewHolder1) {
.....
}else if(holder instanceof ViewHolder2){
.....
}
else if(holder instanceof ViewHolder3){
.....
}
}
@Override
public int getItemCount() {
return mList.size();
}
}
据文章上说的这样写存在三种弊端:
1. 类型检查与类型转型,由于在onCreateViewHolder根据不同类型创建了不同的ViewHolder,所以在onBindViewHolder需要针对不同类型的ViewHolder进行数据绑定与逻辑处理,这导致需要通过instanceof对ViewHolder进行类型检查与类型转型。引用文章上的一句话:
[译]关于Android Adapter,你的实现方式可能一直都有问题中是这样说的
许多年前,我在我的显示器上贴了许多的名言。其中的一个来自 Scott Meyers 写的《Effective C++》 这本书(最好的IT书籍之一),它是这么说的:
不管什么时候,只要你发现自己写的代码类似于 “ if the object is of type T1, then do something, but if it’s of type T2, then do something else ”,就给自己一耳光。
2. 我们的这种写法已经违背了 SOLID 原则中的“开闭准则”。即“对扩展开放,对修改封闭。当我们有新的布局需要添加进来时我们需要在原来的Adpter中添加type标记,要在getItemViewType()方法中添加兼容,要在onCreateViewHolder()、onBindViewHolder()中添加新的if、else处理。
3. 增加了日后的维护成本。随着我们布局的不断增加,我们的Adpter会变得越来越庞大。
针对上边的三个问题,网上已经有大牛进行了相关优化,在此膜拜一下,表示一下感谢,我也自己写了一下代码,后边会附上代码的下载地址,那么下边看一下是怎么进行的优化吧。
先看一下代码:
对所有与列表相关的Model 层数据进行抽象封装(BaseValue),当我们在初始化数据源时就能以List<BaseValue>的形式将不同类型的Model集合在列表中;
将列表类型判断的相关代码抽取到ViewTypeFactory中,同时所有列表类型对应的布局资源都在这个类中进行管理维护,增强了扩展性与可维护性;
这里用到了设计模式中的访问者设计模式,不懂的同学可以先去了解一下相关知识,说一下这里的应用:我们可以在我们的Apdter中实例化一个BaseViewTypeFactory对象,通过获取数据源中对应的model类型调用自己的getLayoutId()方法传入我们的BaseViewTypeFactory对象,看代码可以知道在我们的model实现类的getLayoutId()方法中传入的ViewTypeFactory又调用的相应的type()方法,传入了个自对应的model类型,看Factory实现类中根据不同model重载了相应的type()方法,返回的是model对应的布局的.xml文件在R文件中的id,也就是说在getViewItemType()方法中经过这一系列的调用以后最终返回的为对应的layoutId;及在MyAdpter的OnCreatViewHolder(ViewGrop parent,int viewType)中第二个参数不再是我们自己定义的了,而是当前想要生成的ViewHolder中绑定View的LayoutId;看下面MyAdpter中代码:
现在我们可以直接用viewType创建itemView,但是,itemView创建之后,还是需要进行类型判断,创建不同的ViewHolder,那我们来继续处理,我们将RecyclerView.ViewHolder进行抽象,每一种布局对应一个自己的ViewHolder,下面看代码实现:
public abstract class BaseViewHolder<T> extends RecyclerView.ViewHolder {
private SparseArray<View> mArray;
private View mView;
public BaseViewHolder(View itemView) {
super(itemView);
this.mView = itemView;
mArray = new SparseArray<>();
}
public View getView(int resId) {
View view = mArray.get(resId);
if (view == null) {
view = mView.findViewById(resId);
mArray.put(resId, view);
}
return view;
}
public abstract void setUpView(T modle,int position,MyAdpter adpter);
}
public class ViewHolderOne extends BaseViewHolder<Value1> {
public ViewHolderOne(View itemView) {
super(itemView);
}
@Override
public void setUpView(final Value1 modle, int position, MyAdpter adpter) {
final TextView tv = (TextView) getView(R.id.tv1);
tv.setText(modle.getName());
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(tv.getContext(), modle.getName(), Toast.LENGTH_SHORT).show();
}
});
}
}
public class ViewHolderTwo extends BaseViewHolder<Value2> {
public ViewHolderTwo(View itemView) {
super(itemView);
}
@Override
public void setUpView(Value2 modle, int position, MyAdpter adpter) {
TextView tv= (TextView) getView(R.id.tv2);
tv.setText(modle.getAddress());
}
}
我们继承RecyclerView.ViewHolder做扩展,保存item对应生成的View,通过一个getView()方法用SparseArray做我们要操作的子View的缓存,并且拿到相应的View;这个getView方法是供子类调用的;setUpView()抽象方法子类实现操作自己对应的子View。
定义好了我们的每一种item对应的ViewHolder后,再回到我们的BaseViewTypeFactory
基类添加一个抽象的onCreatViewHolder()方法,ViewTypeFactory类中实现,看下边代码:
public interface BaseViewTypeFactory {
int type(Value1 type);
int type(Value2 type);
int type(Value3 type);
BaseViewHolder creatViewHolder(View v,int viewType);
}
public class ViewTypeFactory implements BaseViewTypeFactory {
private final int oneId = R.layout.viewtype1;
private final int twoId = R.layout.viewtype2;
private final int threeId = R.layout.viewtype3;
@Override
public int type(Value1 type) {
return oneId;
}
@Override
public int type(Value2 type) {
return twoId;
}
@Override
public int type(Value3 type) {
return threeId;
}
@Override
public BaseViewHolder creatViewHolder(View v, int viewType) {
BaseViewHolder mv = null;
if (viewType == oneId)
mv = new ViewHolderOne(v);
else if (viewType == twoId)
mv = new ViewHolderTwo(v);
else if (viewType == threeId)
mv = new ViewHolderThree(v);
return mv;
}
}
这样处理好以后我们就可以在我们adpter类中的onCreatViewHolder()方法中通过我们已经实例好的factory类进行ViewHolder的初始化了看代码:
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = View.inflate(parent.getContext(), viewType, null);
return mFactory.creatViewHolder(v, viewType);
}
分析一下这段代码,首先通过viewType生成View,然后调用creatViewHolder()方法,在方法里根据不同的viewType生成对应的ViewHolder返回;这样一来我们将判断移出了Adpter类,到这里我们的onCreatViewHolder()方法就优化完了,就两行代码,生成View,调用creatViewHolder()生成ViewHolder。是不是感觉Adpter中清新了不少。
然后就是onBindViewHolder()方法,这就简单了,还是看代码:
@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
holder.setUpView(mList.get(position), position, this);
}
就一行代码,因为我们在每一个ViewHolder中实现了自己的setUpView()方法,先拿一个看一下:
@Override
public void setUpView(final Value1 modle, int position, MyAdpter adpter) {
final TextView tv = (TextView) getView(R.id.tv1);
tv.setText(modle.getName());
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(tv.getContext(), modle.getName(), Toast.LENGTH_SHORT).show();
}
});
}
这是ViewHolder1中的setUpView实现,分析一下:我们在Adpter中的数据源中拿到model与对应的position位置信息,调用setUpView(),在ViewHolder1中通过父类中的getView()方法得到要操作的子View,从model中得到值进行操作。
到目前为止我们的多布局RecyclerView就优化完了,看一下我们的Adpter是不是很清晰:
@Override
public int getItemViewType(int position) {
return mList.get(position).getLayoutId(mFactory);
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = View.inflate(parent.getContext(), viewType, null);
return mFactory.creatViewHolder(v, viewType);
}
@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
holder.setUpView(mList.get(position), position, this);
}
下面我们把我们activity中的代码与Adpter中代码也附在下边,布局文件没什么好说的,就是一个RecyclerView,没什么特殊的:
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerview = (RecyclerView) findViewById(R.id.recyclerview);
recyclerview.setLayoutManager(new LinearLayoutManager(this));
recyclerview.setAdapter(new MyAdpter(this));
}
}
public class MyAdpter extends RecyclerView.Adapter<BaseViewHolder> {
private Context mContext;
private List<BaseValue> mList;
private BaseViewTypeFactory mFactory;
public MyAdpter(Context con) {
this.mContext = con;
mFactory = new ViewTypeFactory();
mList = new ArrayList<>();
mList.add(new Value1("刘大"));
mList.add(new Value3("8"));
mList.add(new Value1("王二"));
mList.add(new Value1("张三"));
mList.add(new Value2("北京"));
mList.add(new Value3("11"));
mList.add(new Value1("李四"));
mList.add(new Value2("上海"));
mList.add(new Value1("陈五"));
mList.add(new Value3("32"));
mList.add(new Value1("周六"));
mList.add(new Value2("深圳"));
mList.add(new Value1("小明"));
mList.add(new Value1("小光"));
mList.add(new Value1("小红"));
mList.add(new Value3("48"));
mList.add(new Value2("天津"));
mList.add(new Value2("广州"));
}
@Override
public int getItemViewType(int position) {
return mList.get(position).getLayoutId(mFactory);
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = View.inflate(parent.getContext(), viewType, null);
return mFactory.creatViewHolder(v, viewType);
}
@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
holder.setUpView(mList.get(position), position, this);
}
@Override
public int getItemCount() {
return mList.size();
}
}
我们在adpter创建的时候载入了不同类型的数据,下边看一下运行结果:
当我们有新的类型布局进来后,看看我们需要改哪些地方:
1.新建对应model。
2.做Factory兼容。
3.新建新的ViewHolder,实现setUpView()方法就行了。
是不是很简单,我们的Adpter不用动了,是不是扩展性比原来好多了,而且没有了类型的对比转换,维护性也好了,不用管Adpter了,只需做以上三步,就完成了我们新类型布局的添加。好的,这篇文章就写到这里,如果你感觉说的不是很清楚,可以下载源码阅读源码,希望对你有所帮助!
这里附上Demo的下载地址:http://download.youkuaiyun.com/detail/liuyonglei1314/973648