最近项目中要实现RecyclerView添加头尾,后在度娘的帮助下完成任务;就在自己为此而窃喜的时候,一个念头飞入我的脑海:将添加头尾的逻辑封装到RecyclerView的万能适配器中,实现可以选择添加。真是飞来横祸啊,我最怕这些想法了,一旦心里这样想了,那便是种下了一颗邪恶的种子,让我心里直痒痒,结果在本能的作用下:开始了一个不眠之夜……
本次封装实现了:
1. 加载各种Item布局,并做了缓存(在万能适配器的基础上封装的);
2. 选择添加头或尾(都不加,都加,二选一 均可);
3. item的点击事件;
感觉适配器的构造方法不是很理想:头尾二选一的时候必须保留适配器构造方法的三个泛型 , 并且不能设置null(不用的泛型可设置为String不影响) , 要把不选的那个资源文件设置为空,数据源设置为null,希望有识之士提出更好的写法;
让我很纠结的一点是:要不要将头尾的数据源传递到适配器中:传的话要将头尾的数据源封装成类(JavaBean)并在适配器的构造方法中作为泛型导入,比较麻烦,在通常的情况下可以直接在Activity中将数据设置到控件中,但是考虑到List等一些复杂的情况,我还是觉得该予以保留……你的看法呢?期待你的回复….
/**
* 不添加头或尾的时候
* @param mContext
* @param mBodyDatas
* @param bodyLayoutId
*/
public RecyclerAdapter(Context mContext, List<T> mBodyDatas, int bodyLayoutId) {
this(mContext,bodyLayoutId,mBodyDatas,0,null,0,null);
}
/**
* 添加头或尾的时候
* @param context
* @param bodyLayoutId 主体布局文件
* @param bodyDatas 主体数据源
* @param headerLayoutId 头部布局
* @param headerData 头部数据源
* @param footerLayoutId 尾部布局
* @param footerData 尾部数据源
*/
public RecyclerAdapter(Context context, int bodyLayoutId, List<T> bodyDatas,int headerLayoutId,H headerData,int footerLayoutId,F footerData) {
this.mContext = context;
this.mBodyDatas = bodyDatas;
this.mHeaderData=headerData;
this.mFooterData=footerData;
this.mBodyLayoutId = bodyLayoutId;
this.mHeaderLayoutId = headerLayoutId;
this.mFooterLayoutId = footerLayoutId;
mInflater = LayoutInflater.from(mContext);
//判断是否有头
if (headerLayoutId==0){
mHeaderCount=0;
}else {
mHeaderCount=1;
}
//判断是否有尾
if (footerLayoutId==0){
mBottomCount=0;
}else {
mBottomCount=1;
}
}
————-实现逻辑以及主要方法摘要:————————–
1 . 多布局,根据头尾的个数(目前只支持0或1),对每个Item进行类型判断:
//item类型分三种
public static final int ITEM_TYPE_HEADER = 0;//头
public static final int ITEM_TYPE_CONTENT = 1;//主体
public static final int ITEM_TYPE_BOTTOM = 2;//尾
private int mHeaderCount;//头部View个数
private int mBottomCount;//底部View个数
/**
* 判断position对应的Item的类型
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
int dataItemCount = getContentItemCount();
if (mHeaderCount != 0 && position < mHeaderCount) {
//头部View
return ITEM_TYPE_HEADER;
} else if (mBottomCount != 0 && position >= (mHeaderCount + dataItemCount)) {
//底部View
return ITEM_TYPE_BOTTOM;
} else {
//内容View
return ITEM_TYPE_CONTENT;
}
}
2.根据item的类型加载不同的布局:
@Override
public RecycleHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//根据item的类型加载不同布局文件
if (viewType ==ITEM_TYPE_HEADER) {
return new RecycleHolder(mInflater.inflate(mHeaderLayoutId, parent, false));
} else if (viewType == ITEM_TYPE_CONTENT) {
return new RecycleHolder(mInflater.inflate(mBodyLayoutId, parent, false));
} else if (viewType == ITEM_TYPE_BOTTOM) {
return new RecycleHolder(mInflater.inflate(mFooterLayoutId, parent, false));
}
return null;
}
3.根据item不同的类型添加不同的数据源:
@Override
public void onBindViewHolder(final RecycleHolder holder, int position) {
//根据item的类型实现与不同数据源进行衔接
if (isHeaderView(position)){
convertHeader(holder,mHeaderData,position);
}else if (isBottomView(position)){
convertFooter(holder,mFooterData,position);
}else {
convertBody( holder, mBodyDatas.get(position - mHeaderCount), position);
}
}
4.在Activity中实现不同item加载不同数据源的逻辑:
mRecyclerView.setAdapter(adapter=new RecyclerAdapter<String,String,String>(this,stringLists,R.layout.item_recycle,R.layout.header_recycle,sHeader,R.layout.footer_recycle,sFooter){
//必须重写
@Override
public void convertBody(RecycleHolder holder, String data, int position) {
holder.setText(R.id.tv_item_text,data);
}
//选择重写
@Override
public void convertHeader(RecycleHolder holder, String data, int position) {
holder.setText(R.id.tv_header,sHeader);
}
//选择重写
@Override
public void convertFooter(RecycleHolder holder, String data, int position) {
holder.setText(R.id.tv_footer,sFooter);
}
});
5. 在GridLayout和瀑布流时让头尾宽度显示全屏,不用下面方法则显示一列的宽度
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (adapter.isHeaderView(position) || adapter.isBottomView(position)) ? gridLayoutManager.getSpanCount() : 1;
}
});
————–附效果图以及主要源码:—————–
尾部
注:这里每个使用的是v7包下的CardView,这不是今天的重点,所以布局文件省略。
首先我们来看一下ViewHolder类:
public class RecycleHolder extends RecyclerView.ViewHolder {
/** 用于存储当前item当中的View */
private SparseArray<View> mViews;
public RecycleHolder(View itemView) {
super(itemView);
mViews = new SparseArray<View>();
}
public <T extends View> T findView(int ViewId) {
View view = mViews.get(ViewId);
//集合中没有,则从item当中获取,并存入集合当中
if (view == null) {
view = itemView.findViewById(ViewId);
mViews.put(ViewId, view);
}
return (T) view;
}
public RecycleHolder setText(int viewId, String text) {
TextView tv = findView(viewId);
tv.setText(text);
return this;
}
public RecycleHolder setText(int viewId, int text) {
TextView tv = findView(viewId);
tv.setText(text);
return this;
}
public RecycleHolder setImageResource(int viewId, int ImageId) {
ImageView image = findView(viewId);
image.setImageResource(ImageId);
return this;
}
public RecycleHolder setImageBitmap(int viewId, Bitmap bitmap) {
ImageView imageView= findView(viewId);
imageView.setImageBitmap(bitmap);
return this;
}
public RecycleHolder setImageNet(int viewId, String url) {
final ImageView imageView= findView(viewId);
//使用你所用的网络框架等,这里使用imageloader
// ImageLoader.getInstance().loadImage(url, new SimpleImageLoadingListener() {
//
// @Override
// public void onLoadingComplete(String imageUri, View view,Bitmap loadedImage) {
// super.onLoadingComplete(imageUri, view, loadedImage);
// imageView.setImageBitmap(loadedImage);
//
// }
// @Override
// public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
// super.onLoadingFailed(imageUri, view, failReason);
// }
//
// });
return this;
}
}
接下来来看看今天的猪脚:
**
* Created by Jack on 2016/9/21
*/
public abstract class RecyclerAdapter<T,H,F> extends RecyclerView.Adapter<RecycleHolder> {
private Context mContext;
private List<T> mBodyDatas;
private H mHeaderData;//H 头部数据源的泛型
private F mFooterData;//T 尾部数据源的泛型
private int mBodyLayoutId,mHeaderLayoutId,mFooterLayoutId;
private LayoutInflater mInflater;
//item类型分三种
public static final int ITEM_TYPE_HEADER = 0;//头
public static final int ITEM_TYPE_CONTENT = 1;//主体
public static final int ITEM_TYPE_BOTTOM = 2;//尾
private int mHeaderCount;//头部View个数
private int mBottomCount;//底部View个数
private OnItemClickListener onItemClickListener;//item监听
/**
* 不添加头或尾的时候
* @param mContext
* @param mBodyDatas
* @param bodyLayoutId
*/
public RecyclerAdapter(Context mContext, List<T> mBodyDatas, int bodyLayoutId) {
this(mContext,bodyLayoutId,mBodyDatas,0,null,0,null);
}
/**
* 添加头或尾的时候
* @param context
* @param bodyLayoutId 主体布局文件
* @param bodyDatas 主体数据源
* @param headerLayoutId 头部布局
* @param headerData 头部数据源
* @param footerLayoutId 尾部布局
* @param footerData 尾部数据源
*/
public RecyclerAdapter(Context context, int bodyLayoutId, List<T> bodyDatas,int headerLayoutId,H headerData,int footerLayoutId,F footerData) {
this.mContext = context;
this.mBodyDatas = bodyDatas;
this.mHeaderData=headerData;
this.mFooterData=footerData;
this.mBodyLayoutId = bodyLayoutId;
this.mHeaderLayoutId = headerLayoutId;
this.mFooterLayoutId = footerLayoutId;
mInflater = LayoutInflater.from(mContext);
//判断是否有头
if (headerLayoutId==0){
mHeaderCount=0;
}else {
mHeaderCount=1;
}
//判断是否有尾
if (footerLayoutId==0){
mBottomCount=0;
}else {
mBottomCount=1;
}
}
@Override
public RecycleHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//根据item的类型加载不同布局文件
if (viewType ==ITEM_TYPE_HEADER) {
return new RecycleHolder(mInflater.inflate(mHeaderLayoutId, parent, false));
} else if (viewType == ITEM_TYPE_CONTENT) {
return new RecycleHolder(mInflater.inflate(mBodyLayoutId, parent, false));
} else if (viewType == ITEM_TYPE_BOTTOM) {
return new RecycleHolder(mInflater.inflate(mFooterLayoutId, parent, false));
}
return null;
}
@Override
public void onBindViewHolder(final RecycleHolder holder, int position) {
//根据item的类型实现与不同数据源进行衔接
if (isHeaderView(position)){
convertHeader(holder,mHeaderData,position);
}else if (isBottomView(position)){
convertFooter(holder,mFooterData,position);
}else {
convertBody( holder, mBodyDatas.get(position - mHeaderCount), position);
}
//为子项布局设置监听事件
if (onItemClickListener != null) {
//设置背景
//holder.itemView.setBackgroundResource(R.drawable.recycler_bg);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//注意,这里的position不要用上面参数中的position,会出现位置错乱\
onItemClickListener.OnItemClickListener(holder.itemView, holder.getLayoutPosition());
}
});
}
}
/**
* 将主体数据源添加到主体上-----必须要重写
* @param holder
* @param data
* @param position
*/
public abstract void convertBody(RecycleHolder holder, T data, int position);
/**
* 将头部数据源添加到头部-----recyclerView添加头部时重写
* @param holder
* @param data
* @param position
*/
public void convertHeader(RecycleHolder holder, H data, int position){
}
/**
* 将尾部数据源添加到尾部------recyclerView添加尾部时重写
* @param holder
* @param data
* @param position
*/
public void convertFooter(RecycleHolder holder, F data, int position){
}
/**
* 返回Item的个数要考虑添加头和尾的个数
* @return
*/
@Override
public int getItemCount() {
return mBodyDatas.size()+mHeaderCount+mBottomCount;
}
/**
* 返回主体item的个数
* @return
*/
public int getContentItemCount(){
return mBodyDatas.size();
}
/**
* 判断当前item是否是HeadView
* @param position
* @return
*/
public boolean isHeaderView(int position) {
return mHeaderCount != 0 && position < mHeaderCount;
}
/**
* 判断当前item是否是FootView
* @param position
* @return
*/
public boolean isBottomView(int position) {
return mBottomCount != 0 && position >= (mHeaderCount + getContentItemCount());
}
/**
* 判断position对应的Item的类型
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
int dataItemCount = getContentItemCount();
if (mHeaderCount != 0 && position < mHeaderCount) {
//头部View
return ITEM_TYPE_HEADER;
} else if (mBottomCount != 0 && position >= (mHeaderCount + dataItemCount)) {
//底部View
return ITEM_TYPE_BOTTOM;
} else {
//内容View
return ITEM_TYPE_CONTENT;
}
}
/**
* 为recycler View设置item的点击事件
* @param onItemClickListener
*/
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
//点击事件监听接口
public interface OnItemClickListener {
void OnItemClickListener(View view, int position);
}
}
这里的代码都经过了笔者精心注释,所以就不多解释了;
最后我们来看看Activity类:
public class MainActivity extends AppCompatActivity {
RecyclerView mRecyclerView;
GridLayoutManager gridLayoutManager;
RecyclerAdapter adapter;
String sHeader="我是头头,我爱你";
String sFooter="我是伟哥,我喜欢你离我远点";
List<String> stringLists;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initRecycler();
initRecyclerClick();
}
private void initRecyclerClick() {
adapter.setOnItemClickListener(new RecyclerAdapter.OnItemClickListener() {
@Override
public void OnItemClickListener(View view, int position) {
Toast.makeText(MainActivity.this, position+"我被点击了", Toast.LENGTH_SHORT).show();
}
});
}
private void initRecycler() {
mRecyclerView= (RecyclerView) findViewById(R.id.recyclerView);
stringLists=new ArrayList<>();
for (int i=0;i<30;i++) {
stringLists.add("天下第" + i);
}
//List布局
// layoutManager=new LinearLayoutManager(this);
// layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
// mRecyclerView.setLayoutManager(layoutManager);
// mRecyclerView.setAdapter(adapter=new HeaderBottomAdapter(this));
//Grid布局
gridLayoutManager=new GridLayoutManager(MainActivity.this, 2);
mRecyclerView.setLayoutManager(gridLayoutManager);//这里用线性宫格显示 类似于grid view
mRecyclerView.setAdapter(adapter=new RecyclerAdapter<String,String,String>(this,R.layout.item_recycle,stringLists,R.layout.header_recycle,sHeader,R.layout.footer_recycle,sFooter){
@Override
public void convertBody(RecycleHolder holder, String data, int position) {
holder.setText(R.id.tv_item_text,data);
}
@Override
public void convertHeader(RecycleHolder holder, String data, int position) {
holder.setText(R.id.tv_header,sHeader);
}
@Override
public void convertFooter(RecycleHolder holder, String data, int position) {
holder.setText(R.id.tv_footer,sFooter);
}
});
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (adapter.isHeaderView(position) || adapter.isBottomView(position)) ? gridLayoutManager.getSpanCount() : 1;
}
});
}
}
这里就是要注意:如果头尾只一个:不添加的泛型不能填null, 可填String无不良反应,布局文件填0,数据源填null。
eg :
adapter=new RecyclerAdapter<String,null,String>(this,R.layout.item_recycle,stringLists, 0 ,null,R.layout.footer_recycle,sFooter){
...
@Override
public void convertFooter(RecycleHolder holder, String data, int position) {
holder.setText(R.id.tv_footer,sFooter);
}
}