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

最近项目中要实现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);
         }

}

笔者正是考虑到数据源的潜在的复杂性,数据源或适配器的位置比较灵活,所以主观上还是觉得应该将头尾的数据源传递到适配器中,相信聪明的你一定会随机应变,实现更酷更炫的效果,笔者真心期待你的灵感的鞭策。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值