您的位置:首页 > 其它

RecyclerView万能适配器加强版———可选择添加头和尾(含点击事件)

2016-09-21 03:46 507 查看
最近项目中要实现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);
}

}


笔者正是考虑到数据源的潜在的复杂性,数据源或适配器的位置比较灵活,所以主观上还是觉得应该将头尾的数据源传递到适配器中,相信聪明的你一定会随机应变,实现更酷更炫的效果,笔者真心期待你的灵感的鞭策。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: