感谢:五谷观精分道长 对该控件的开源
该控件的地址:
https://github.com/Jude95/EasyRecyclerView
引言:在一个页面中,出现头部为某种布局,下方是一个A item列表,A滑动结束后紧接着下方又是一个B item列表,后面还有C...,或者是ABC三种item布局相互掺杂的出现在一个列表中的情况,最后再以一个尾布局结尾,那么这个页面就拥有头部和尾部,中间又加上了多种item项类型的列表,算是一个比较复杂但是又常使用的列表了,同时有下拉刷新和上拉加载的功能。
使用:
步骤一、
添加依赖:
compile 'com.android.support:recyclerview-v7:23.0.1'
compile 'com.jude:easyrecyclerview:4.4.2'
步骤二、
自定义一个基类适配器继承自ERV的适配器:
以下自定义适配器基本满足要求,可看一下方法注释,直接拷贝后,让列表页面的适配器继承使用即可
public class EasyRecyclerArrayAdapter extends RecyclerArrayAdapter {
private boolean mCanScrollVertically = true;
private Context mContext;
//重写构造器
public EasyRecyclerArrayAdapter(Context context) {
super(context);
mContext = context;
}
public void setCanScrollVertically(boolean canScrollVertically) {
mCanScrollVertically = canScrollVertically;
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false) {
@Override
public boolean canScrollVertically() {
return mCanScrollVertically;
}
};
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
}
@Override
public BaseViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) {
return super.onCreateViewHolder(parent, viewType);
}
//更新item行数据状态(如点赞状态,收藏状态)
@Override
public void update(Object object, int pos) {
mCanScrollVertically = false;
mObjects.set(pos,object);
notifyItemChanged(pos + headers.size());
mCanScrollVertically = true;
}
@Override
public void addAll(Object[] items) {
mCanScrollVertically = false;
super.addAll(items);
mCanScrollVertically = true;
}
//加载请求获取一页list数据源集合(即常规添加数据源集合)
@Override
public void addAll(Collection collection) {
mCanScrollVertically = false;
super.addAll(collection);
mCanScrollVertically = true;
}
//接在上一波list列表后面单独插入一行item
@Override
public void add(Object object) {
mCanScrollVertically = false;
super.add(object);
mCanScrollVertically = true;
}
//即时插入一行item数据显示(一般用于新增评论)
@Override
public void insert(Object object, int index) {
mCanScrollVertically = false;
super.insert(object, index);
mCanScrollVertically = true;
}
@Override
public void insertAll(Object[] object, int index) {
mCanScrollVertically = false;
super.insertAll(object, index);
mCanScrollVertically = true;
}
@Override
public void insertAll(Collection object, int index) {
mCanScrollVertically = false;
super.insertAll(object, index);
mCanScrollVertically = true;
}
//添加尾部
@Override
public void addFooter(ItemView view) {
mCanScrollVertically = false;
super.addFooter(view);
}
//添加头部
@Override
public void addHeader(ItemView view) {
mCanScrollVertically = false;
super.addHeader(view);
mCanScrollVertically = true;
}
//即时删除
@Override
public void remove(int position) {
mCanScrollVertically = false;
super.remove(position);
mCanScrollVertically = true;
}
//即时删除,与上方的不同的就是看删除的时候所在的方法中提供了什么参数
@Override
public void remove(Object object) {
mCanScrollVertically = false;
super.remove(object);
mCanScrollVertically = true;
}
}
步骤三、
看一下将要实现的效果:
如下图所示,其上方为一个导航,下方上半部分是一个header, 下方是一个list列表,目前看起来就是一个RecyclerView添加头部的布局,只是,当点击猫文,猫贴士时,会切换列表item内的布局和数据的变化,也就是说,该布局,在最外层只需要一个EasyRecyclerView控件,只需要使用一个适配器,三种不同的item布局及其内部的数据展示使用三个ViewHolder即可,效果图和思路就是这些,下面看具体实现步骤
步骤四、
实现:
1:总页面布局
就用一个EasyRecyclerView控件去实现就好了
2、本页面的header布局
小控件有点多就不写了,参照上图橘色线上部分布局就是一个header布局,正常写XML就可以。
3、编写页面的适配器SuckCatFragmentAdapter
首先理一下思路,前文说了,本页面的列表虽然有三种item的行布局(猫文/猫图/猫贴士),实际上只用一个适配器,只是不同的item布局是使用ViewHolder去区分的的,所以主要就是ViewHolder的区别。
该适配器主要是为了写三个方法:
1)构造方法
2)返回当前行item所属的类型
3)根据方法2返回的类型去返回对应的ViewHolder
本例适配器如下:
public class SuckCatFragmentAdapter extends EasyRecyclerArrayAdapter {//继承自之前自定义的那个适配器
//ViewHolder类型
public static final int TYPE_INVALID = 0; //抛出异常
public static final int TYPE_IMAGE = 1; //猫图
public static final int TYPE_ARTICLE = 2; //猫文
public static final int TYPE_TIPS = 3; //猫贴士(和猫文用的是同一个Bean)
//1)构造方法,为适配器传入上下文参数
private Context mContext;
public SuckCatFragmentAdapter(Context context) {
super(context);
mContext = context;
}
//2)返回当前行item所属的类型
@Override
public int getViewType(int position) {
Object object = getItem(position);//渲染到当前行item时,获取当前行的对象
if (object instanceof CatCircleCategoryDetailBean){
return TYPE_IMAGE;
}else if (object instanceof CatCircleCategoryTipsBean){
if (((CatCircleCategoryTipsBean) object).getCatCircleCategoryType().getId().equals("2")){//猫文
return TYPE_ARTICLE;
}else if (((CatCircleCategoryTipsBean) object).getCatCircleCategoryType().getId().equals("3")){//猫贴士
return TYPE_TIPS;
}
}
return TYPE_INVALID;//抛出异常type
}
//3)根据方法2)返回ViewHolder的Type,决定返回哪个ViewHolder
@Override
public BaseViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType){
case TYPE_IMAGE:
//返回猫图ViewHolder
CatImageViewHolder catImageViewHolder = new CatImageViewHolder(parent);
return catImageViewHolder;
case TYPE_ARTICLE:
CatArticleViewHolder catArticleViewHolder = new CatArticleViewHolder(parent);
return catArticleViewHolder;
case TYPE_TIPS:
CatTipsViewHolder catTipsViewHolder = new CatTipsViewHolder(parent);
//调用ViewHolder里面的方法,(如:传值,item内部控件的点击事件回调等)
catTipsViewHolder.setmContext(mContext);
//内部控件的点击事件回调
catTipsViewHolder.setmOnClick(new CatTipsViewHolder.ItemOnClick() {
@Override
public void onClick(int position, CatCircleCategoryTipsBean t) {//注意这个positon是减去header的数量后的,即第一行item要保证是0
Toasty.info(mContext, position + "").show();
}
});
//向ViewHolder传入本Adapter的对象
catTipsViewHolder.setSuckCatFragmentAdapter(this);
return catTipsViewHolder;
//抛异常
default:
throw new InvalidParameterException();
}
}}
从以上适配器的写法来看,是很简单的,我们只要为item写好布局和ViewHolder类即可。从以上来看,第三个的CatTipsViewHolder的稍微复杂那么点,那接下来就看这个CatTipsViewHolder。
3、编写item行的布局及其ViewHolder
这里以CatTipsViewHolder为例:
1)item行布局XML就不需要说了
2)CatTipsViewHolder如下:(只举里面一个控件为例)
public class CatTipsViewHolder extends BaseViewHolder {
private SuckCatFragmentAdapter suckCatFragmentAdapter;
public void setSuckCatFragmentAdapter(SuckCatFragmentAdapter suckCatFragmentAdapter) {
this.suckCatFragmentAdapter = suckCatFragmentAdapter;
}
//点击事件的转嫁接口
public interface ItemOnClick{
void onClick(int position, CatCircleCategoryTipsBean t);
}
private ItemOnClick mOnClick = null;
public void setmOnClick(ItemOnClick mOnClick) {
this.mOnClick = mOnClick;
}
//传入上下文对象
private Context mContext;
public void setmContext(Context mContext) {
this.mContext = mContext;
mTipAdapter = new TipsAdapter(mContext);
}
private TextView mTvTitle;
private ImageView mImNews;
public CatTipsViewHolder(ViewGroup parent) {//注意参数
super(parent, R.layout.listadapter_circle_tip_header);//CatTipsViewHolder的布局
mTvTitle = (TextView) itemView.findViewById(R.id.tv_title);
mImNews = (ImageView) itemView.findViewById(R.id.im_News);
}
@Override
public void setData(final CatCircleCategoryTipsBean categoryTipsBean) {//item行的类对象
//标题
if (categoryTipsBean.isNew()) {
mImNews.setVisibility(View.VISIBLE);
mTvTitle.setText(" " + categoryTipsBean.getTitle());
} else {
mImNews.setVisibility(View.GONE);
mTvTitle.setText(categoryTipsBean.getTitle());
}
//item内部控件点击事件(点击标题)
mTvTitle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//可以将点击事件转嫁出去
if (mOnClick != null){
//注意参数是要减去header的数量的,才能保证列表的第一行item是从0开始的
mOnClick.onClick(getAdapterPosition() - suckCatFragmentAdapter.getHeaderCount(), categoryTipsBean);
}
//也可以不走转嫁,直接处理该点击事件
//Toast.makeText(mContext, "item行内部控件点击事件", Toast.LENGTH_SHORT).show();
}
});
}
}
4、在页面中用代码实现
上文说了页面总布局、header布局、item布局;适配器Adapter;Adapter中的ViewHolder;那么下面就要看看在页面中如何实现这个复杂的布局。
本例是在一个Fragment中写的(Activity也一样使用),实现如下:
public class CatCircleFragment extends BaseFragment{
//声明页面总布局的一些控件
private View mViewV;
private SuckCatFragmentAdapter mAdapter;
private EasyRecyclerView mEasyRecyclerView;
private int mPageSize = 15;//一页加载的数据量
private int mPage = 0;//从mPage页开始加载
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mViewV = inflater.inflate(R.layout.fragment_circle_cat, container, false);
initView();
initDataList();
return mViewV;
}
private void initView(){
//ERV的使用主要就看该方法
configRecycleView();
}
private void initDataList(){
//加载列表数据
loadCatCircleList(circleType, tag);
}
/**
* 设置ERV相关(主要就是这个方法)
*/
private void configRecycleView(){
//给ERV添加布局管理器
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
mEasyRecyclerView = (EasyRecyclerView) mViewV.findViewById(R.id.erv_suck_erv);
mEasyRecyclerView.setLayoutManager(layoutManager);
mAdapter = new SuckCatFragmentAdapter(getActivity());
mEasyRecyclerView.setAdapterWithProgress(mAdapter);//将Adapte关联到ERV
//添加头部
mAdapter.addHeader(new RecyclerArrayAdapter.ItemView() {
@Override
public View onCreateView(ViewGroup parent) {
//头部视图
View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_circle_cat_list_header, parent, false);
//头部各种控件的初始化(findViewById...),该方法只举一个控件为例
initHeaderView(view);
//获取头部的数据
newCatPrizeAndWeeklySelect();
//头部各种控件的点击事件,不举例了
headerClickEvent(new Bundle());
return view;
}
@Override
public void onBindView(View headerView) {
((ViewGroup)headerView).requestDisallowInterceptTouchEvent(true);
}
});
//添加尾部,按照上述添加头部方法即可。只是方法换成: mAdapter.addFooter();
//item行点击事件
mAdapter.setOnItemClickListener(new RecyclerArrayAdapter.OnItemClickListener() {
@Override
public void onItemClick(int position) {
//点击行点击事件操作一波
}
});
//下拉刷新
mEasyRecyclerView.setRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
mPage = 0;
loadCatCircleList(circleType, tag);//获取一页数据
}
});
//上拉加载,第一个参数是正在加载中的提示布局,可自定义该布局
mAdapter.setMore(R.layout.recycler_view_more, new RecyclerArrayAdapter.OnLoadMoreListener() {
@Override
public void onLoadMore() {
mPage++;
loadCatCircleList(circleType, tag);
}
});
//上拉加载结束后的提示语视图(本例是自定义的,可以用默认的)
View noMoreView = LayoutInflater.from(getActivity()).inflate(R.layout.cat_circle_recycler_view_no_more, null, false);
mToMessageTV = (TextVienoMoreView.findViewById(R.id.tv_to_message_for_us);
mToMessageTV.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//还可以点击里面的控件操作一波
}
});
//不要忘了这茬,设置加载完毕的提示。这个参数是上面的布局,有默认的,编辑器
mAdapter.setNoMore(noMoreView);
}
/**
* 请求列表数据
* 本方法是本例适用,不同项目也是大同小异,主要看是如何将数据加载到列表中的
*/
private void loadCatCircleList(final String type, final String tag){//1猫图,2猫文,3猫贴士
circleType = type;
HashMap params = new HashMap<>();
params.put("catCircleCategoryTypeId", circleType);
params.put("pageIndex", mPage + "");
params.put("pageSize", mPageSize + "");
manager.post(后台接口地址, new HttpCallback() {
@Override
public void onSuccess(int code, String value, String msg, String page) {
if (code != NetworkAPI.RequestSucceed) {
Toasty.error(CatCircleFragment.this.getActivity(), msg).show();
return;
}
if (mPage == 0) {
mAdapter.removeAll();//刷新的时候清空之前的数据
}
//根据不同type请求不同的数据,放入不同的Bean中
if (type.equals(CAT_IMAGE)) {
List circleList = GsonUtil.toList(value, CatCircleCategoryDetailBean.class);
mAdapter.addAll(circleList);//添加数据源circleList到列表中就是这个方法,本文只是讲解使用方法,更多具体的还是看该控件作者的源码比较好
} else if (type.equals(CAT_ARTICLE)) {
List circleList = GsonUtil.toList(value, CatCircleCategoryTipsBean.class);
mAdapter.addAll(circleList);
} else if (type.equals(CAT_TIPS)) {
List circleList = GsonUtil.toList(value, CatCircleCategoryTipsBean.class);
mAdapter.addAll(circleList);
}
}
@Override
public void onFailure(String value) {
Toasty.error(CatCircleFragment.this.getActivity(), value).show();
}
}, params);
}
}
小结:
EasyRecyclerView的使用大体上和RecyclerView差不多,但是其带有下拉刷新和上拉加载以及在同一个页面使用一个列表和适配器,配合多个ViewHolder处理各种不同的item布局时的便捷性,让我选择了它。其实以上只是一些基本的使用,但是也可以满足大多数的业务需求了,还有更多好用的功能,可以去下载其demo学习。
结束。