RecyclerView的封装使用---轻松简化代码
前言
网上有很多封装RecyclerView的方法,虽然也避免了很多重复的代码,但是依然还是需要在Adapter里面根据position进行判断来确定类型,并且需要在oncreateView()里面根据itemtype来进行操作。并且增加或者删除一个类型的item比较复杂。所以就学习了本文介绍的方法,写下博客便于日后复习。
- 1 首先我们先对ViewHolder进行封装,目的是为了不需要频繁的去获取控件,使用SparseArray集合( SparseArray(稀疏数组).他是Android内部特有的api,标准的jdk是没有这个类的.在Android内部用来替代HashMap<Integer,E>这种形式,使用SparseArray更加节省内存空间的使用,SparseArray也是以key和value对数据进行保存的.使用的时候只需要指定value的类型即可.并且key不需要封装成对象类型.)来对子View进行维护,并且使用泛型来定义查找view的返回值,以方便子类调用,而不需要每次都创建一个ViewHolder,代码如下:
package packageadapter.eternallyliu.com.baseadapterpackage.Base;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
/**
* Created by liupei on 2017/4/24.
* 对ViewHolder进行了一个封装,防止频繁创建ViewHolder对象
*/
public class EtBaseViewHolder extends RecyclerView.ViewHolder{
public SparseArray<View> Views;
public View ItemView;
public EtBaseViewHolder(View itemView) {
super(itemView);
this.ItemView = itemView;
Views = new SparseArray<>();
}
public <V extends View> V retrieveView(int resId) {
View view = Views.get(resId);
if (view == null) {
view = ItemView.findViewById(resId);
Views.put(resId, view);
}
return (V) view;
}
public TextView getTextView(int resId) {
return retrieveView(resId);
}
public Button getButton(int resId) {
return retrieveView(resId);
}
public ImageView getImageView(int resId) {
return retrieveView(resId);
}
public View getView(int resId) {
return retrieveView(resId);
}
}
通过上面代码我们可以看出,我们使用SparseArray集合来维护子View,进而我们在以后使用的时候如果没有创建添加到集合中,如果已经获取过了,就直接从集合中获取,BaseViewHolder中主要进行的就是子View的获取,为Adapter绑定数据做准备。
- 2 上面我们已经封装了BaseViewHolder,接下来我们就需要对Adapter进行封装,看到这你可能会问,这不跟网上大众的封装方法一样吗,你仔细看下面的代码:
package packageadapter.eternallyliu.com.baseadapterpackage.Base;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
import java.util.List;
/**
* Created by liupei on 2017/4/24.
*/
//基类的Adapter
public class EtBaseAdapter<C extends EtCell> extends RecyclerView.Adapter<EtBaseViewHolder> {
public List<C> mData;
public Context mContext;
public EtBaseAdapter(Context context, List<C> mData) {
this.mData = mData;
this.mContext = context;
}
@Override
public EtBaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
for (int i = 0; i < getItemCount(); i++) {
if (viewType == mData.get(i).getItemType()) {
return mData.get(i).onCreateViewHolder(parent, viewType);
}
}
throw new RuntimeException("Wrong viewType");
}
@Override
public void onBindViewHolder(EtBaseViewHolder holder, int position) {
onBeforeBindData();
mData.get(position).BindData(holder, position);
}
@Override
public int getItemCount() {
return mData.size();
}
@Override
public int getItemViewType(int position) {
return mData.get(position).getItemType();
}
public void add(C cell) {
add(mData.size(), cell);
}
public void add(int index, C cell) {
mData.add(index, cell);
notifyItemChanged(index);
}
public void addAll(List<C> cells) {
if (cells == null || cells.size() <= 0) {
return;
}
mData.addAll(cells);
notifyItemRangeChanged(mData.size() - cells.size(), cells.size());
}
public void addAll(int index,List<C> cells) {
if (cells == null || cells.size() <= 0) {
return;
}
mData.addAll(index, cells);
notifyItemRangeChanged(index,cells.size());
}
public void remove(C cell) {
remove(mData.indexOf(cell));
}
public void remove(int index) {
mData.remove(index);
notifyItemRemoved(index);
}
public void remove(int start, int Count) {
if (start + Count >= mData.size()) {
return;
}
for (int i = start;i<Count;i++) {
mData.remove(i);
}
notifyItemRangeChanged(start,Count);
}
public void clear() {
mData.clear();
notifyDataSetChanged();
}
public void onBeforeBindData(){
//子类如果需要在ViewHolder绑定数据之前做些操作的话可以重写该方法
};
}
你有没有发现我们这里的数据集合里面存的不是我们从服务器获取到的数据实体对象,而是一个cell,这个cell下面我们会进行说明,这里就先把他理解成为一个item本身,可以看到无论是OnCreateViewHolder方法还是onBindViewHolder以及getItemViewType方法,其内部都调用了list数据集合中cell本身自己的方法,这说明我们已经把所有的操作全部都放在cell内部去执行了,包括这个cell对应什么布局类型,创建ViewHolder,绑定数据到View上等等,所以接下来就到了本文的重点,什么是cell,为什么要用它,用它有什么好处?总结一下,我们封装的Adapter中进行了三个方法的重写,以及对存放Cell(可以看成一个条目本身)集合的添加删除处理操作等。
- 3 创建Cell对象,我们首先创建一个基类Cell,在内部我们定义了一些抽象方法,这些抽象方法交由其子类,也就是具体需要显示在Recyclerview上面的条目cell去根据需求来实现。代码如下:
package packageadapter.eternallyliu.com.baseadapterpackage.Base;
import android.view.ViewGroup;
/**
* Created by liupei on 2017/4/24.
* 通过cell对象本身来决定需要使用哪个布局,使用什么数据
*/
public interface BaseCell{
//释放资源
public abstract void releaseResouce();
//获取到当前cell的item类型
public abstract int getItemType();
//创建当前cell对应的ViewHolder
public abstract EtBaseViewHolder onCreateViewHolder(ViewGroup parent,int ViewType);
//绑定数据
public abstract void BindData(EtBaseViewHolder holder,int position);
}
看到这估计你就明白了:我们之所以要定义cell,就是为了在多种Item类型的情况下,可以看到,我们的Adapter中的集合存的并不是我们需要显示的数据,而是一个一个的Cell,也就说如果我们的item类型多变,我只需要将对应的Cell一个一个添加到Adapter的数据集合中去即可,剩下的数据绑定,布局显示,只需要在Cell内部去实现,这样无论多么复杂的需求页面,我们也不需要在Adapter中进行繁琐的判断了,看到这有没有一种恍然大明白的感觉。
通过ViewHolder,Adapter,Cell三者的协同配合,就避免了冗余的代码,并且还使item的添加删除更加灵活。
- 4 我们接下来实现一个简单的SimpleAdapter,主要是在其中添加了加载中,加载为空,加载失败,加载更多等Cell的布局,Cell的代码就不再赘述了,可以看我最后的GitHub:
package packageadapter.eternallyliu.com.baseadapterpackage.Base;
import android.content.Context;
import android.view.View;
import java.util.List;
import packageadapter.eternallyliu.com.baseadapterpackage.Base.Cell.EmptyCell;
import packageadapter.eternallyliu.com.baseadapterpackage.Base.Cell.ErrorCell;
import packageadapter.eternallyliu.com.baseadapterpackage.Base.Cell.LoadingCell;
import packageadapter.eternallyliu.com.baseadapterpackage.Base.Cell.LoadingMoreCell;
/**
* Created by liupei on 2017/4/24.
*/
public class EtSimpleAdapter extends EtBaseAdapter{
private static final int DATA_LOADING = 0;
private static final int DATA_EMPTY= 1;
private static final int DATA_ERROR = 2;
private static final int DATA_LOADING_MORE = 3;
private static final int DATA_SHOW = 4;
private LoadingCell mLoadingCell;
private LoadingMoreCell mLoadingMoreCell;
private EmptyCell mEmptyCell;
private ErrorCell mErrorCell;
private boolean isLoadMore = false;
public EtSimpleAdapter(Context context, List mData) {
super(context, mData);
mLoadingCell = new LoadingCell(mContext, null);
mLoadingMoreCell = new LoadingMoreCell(mContext, null);
mEmptyCell = new EmptyCell(mContext, null);
mErrorCell = new ErrorCell(mContext, null);
}
public void showLoading() {
clear();
mData.add(mLoadingCell);
notifyDataSetChanged();
}
public void showLoading(View loadingView) {
if (loadingView == null) {
showLoading();
return;
}
clear();
mLoadingCell.setLoadindView(loadingView);
mData.add(mLoadingCell);
}
//这里有一种不常用的情况:加载数据的cell展示中时,依然保留一定数量的数据
public void showLoadind(int saveCount) {
if (saveCount <= 0 || saveCount > mData.size()) {
return;
}
remove(saveCount, mData.size() - saveCount);
if (mData.contains(mLoadingCell)) {
remove(mLoadingCell);
}
add(mLoadingCell);
}
public void hideLoading() {
if (mData.contains(mLoadingCell)) {
remove(mLoadingCell);
}
}
public void showError() {
clear();
add(mErrorCell);
}
//这里有一种不常用的情况:加载数据的cell的过程中如果出错了,依然保留一定数量的数据
public void showError(int saveCount) {
if (saveCount <= 0 || saveCount > mData.size()) {
return;
}
remove(saveCount, mData.size() - saveCount);
if (mData.contains(mErrorCell)) {
remove(mErrorCell);
}
add(mErrorCell);
}
public void hideError() {
if (mData.contains(mErrorCell)) {
remove(mErrorCell);
}
}
public void showLoadMore() {
add(mLoadingMoreCell);
isLoadMore = true;
}
public void hideLoadMore() {
if (mData.contains(mLoadingMoreCell)) {
return;
}
remove(mLoadingMoreCell);
isLoadMore = false;
}
public boolean isLoadMore() {
return isLoadMore;
}
public void showEmpty() {
clear();
add(mEmptyCell);
}
//这里有一种不常用的情况:加载数据的cell的过程中如果为空,依然保留一定数量的数据
public void showEmpty(int saveCount) {
if (saveCount <= 0 || saveCount > mData.size()) {
return;
}
remove(saveCount, mData.size() - saveCount);
if (mData.contains(mEmptyCell)) {
remove(mEmptyCell);
}
add(mEmptyCell);
}
public void hideEmpty() {
if (mData.contains(mErrorCell)) {
remove(mErrorCell);
}
}
}
- 5 最后我们结合baseCell的基础实现类来描述一下cell,先看代码:
package packageadapter.eternallyliu.com.baseadapterpackage.Base;
import android.content.Context;
import android.view.LayoutInflater;
import java.util.List;
/**
* Created by liupei on 2017/4/24.
*/
public abstract class EtCell<T> implements BaseCell{
public T mData;
public LayoutInflater inflater;
public Context mContext;
//创建cell对象的时候需要把数据传进来
public EtCell(Context context,T mData) {
this.mData = mData;
this.mContext = context;
inflater = LayoutInflater.from(mContext);
}
@Override
public void releaseResouce() {
//这里实现了释放资源的方法,如果子类对象有需要的话就可以重写该方法不需要每次都去实现了
}
}
可以看到,我们这里有一个泛型T,T表示我们需要传入的对应该Cell的数据对象,这样以后各种类型的cell就可以直接继承EtCell,进而根据传入的数据对象来填充自己的界面,也实现了释放资源的方法,可以使子类根据需求来重写,不必每次都要实现。
最后附上项目GitHub:https://github.com/EternallyLiu/BaseAdapterPackage
参考资料
http://www.jianshu.com/p/727c18f4bf20
http://www.tuicool.com/articles/YRZRzin