RecyclerView多种布局封装优化(雷惊风)

   今天看见一篇文章,讲到了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进行数据绑定与逻辑处理,这导致需要通过instanceofViewHolder进行类型检查与类型转型。引用文章上的一句话:
[]关于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()中添加新的ifelse处理。

       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;及在MyAdpterOnCreatViewHolderViewGrop parent,int viewType)中第二个参数不再是我们自己定义的了,而是当前想要生成的ViewHolder中绑定ViewLayoutId;看下面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.csdn.net/detail/liuyonglei1314/973648

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值