Android RecyclerView添加头部和尾部

转载请标明出处:
http://blog.csdn.net/xuehuayous/article/details/50498345;
本文出自:【Kevin.zhou的博客】

前言:在使用RecyclerView替换之前常用的ListView开发的时候,我们会发现一个问题,RecyclerView中没有提供给我们添加头部尾部的方法,那么我们就可以参考ListView的实现方式来为RecyclerView扩展,使其支持添加头部和添加尾部。

一、 最终效果

    我们希望RecyclerView提供如下两个方法,addHeaderView(View view); addFooterView(View view);先来瞄一眼最终的效果:



二、 ListView实现方案

    
    既然我们是看ListView提供了这个方法才决定对RecyclerView进行改造的,那么ListView中是怎么处理的呢?

public void addHeaderView(View v) {
    addHeaderView(v, null, true);
}
在addHeaderView(View v)方法中调用了一个三参数的addHeaderView方法:
public void addHeaderView(View v, Object data, boolean isSelectable) {
	final FixedViewInfo info = new FixedViewInfo();
	info.view = v;
	info.data = data;
	info.isSelectable = isSelectable;
	mHeaderViewInfos.add(info);
	mAreAllItemsSelectable &= isSelectable;

	// Wrap the adapter if it wasn't already wrapped.
	if (mAdapter != null) {
		if (!(mAdapter instanceof HeaderViewListAdapter)) {
			mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
		}

		// In the case of re-adding a header view, or adding one later on,
		// we need to notify the observer.
		if (mDataSetObserver != null) {
			mDataSetObserver.onChanged();
		}
	}
}
可以看到这里使用了一个叫 HeaderViewListAdapter的包装类,并且使用了一个FixedViewInfo的内部类来保存添加的信息。既然是一个ListView的Adapter那我们自然最关心的就是该Adapter中的getCount()、getView()等方法:
public int getCount() {
	if (mAdapter != null) {
		return getFootersCount() + getHeadersCount() + mAdapter.getCount();
	} else {
		return getFootersCount() + getHeadersCount();
	}
}
public View getView(int position, View convertView, ViewGroup parent) {
	// Header (negative positions will throw an IndexOutOfBoundsException)
	int numHeaders = getHeadersCount();
	if (position < numHeaders) {
		return mHeaderViewInfos.get(position).view;
	}

	// Adapter
	final int adjPosition = position - numHeaders;
	int adapterCount = 0;
	if (mAdapter != null) {
		adapterCount = mAdapter.getCount();
		if (adjPosition < adapterCount) {
			return mAdapter.getView(adjPosition, convertView, parent);
		}
	}

	// Footer (off-limits positions will throw an IndexOutOfBoundsException)
	return mFooterViewInfos.get(adjPosition - adapterCount).view;
}
通过以上简单的分析,我们基本就知道了ListView是如何实现添加头部和添加尾部的,就是给ListView的Adapter使用了一个包装类,对头部和尾部条目做了处理,其余还是使用的被包装类的方法。

三、WrapAdapter包装类编写

    
    既然了解了ListView添加头部和尾部的实现方案,那么我们就按照该方案来实现自己的Recycler.Adapter的包装类吧。
    我们知道ListView的Adapter中我们通常关系的是getCount(),getView()等方法,那么Recycler.Adapter中我们关心哪些方法呢?没错就是onCreateViewHolder()、onBindViewHolder()、getItemCount()、getItemViewType(),那么只需要对这几个方法包装下就好了。

1. 添加View描述类创建
    把ListView中描述头部尾部的FixedViewInfo类拿过来稍作修改变为我们的描述类;
/**
 * A class that represents a fixed view in a list, for example a header at the top
 * or a footer at the bottom.
 */
public class FixedViewInfo {
	/** The view to add to the list */
	public View view;
	/** The data backing the view. This is returned from {RecyclerView.Adapter#getItemViewType(int)}. */
	public int viewType;
}

2. 创建存储头部尾部集合
private ArrayList<FixedViewInfo> mHeaderViewInfos = new ArrayList<>();
private ArrayList<FixedViewInfo> mFooterViewInfos = new ArrayList<>();

3. 修改getItemCount()方法
@Override
public int getItemCount() {
	return mHeaderViewInfos.size() + mRealAdapter.getItemCount() + mFooterViewInfos.size();
}
比较简单,只需要把真正的AdapterView的个数加上头部和尾部的个数就可以啦。

4. 修改getItemViewType()方法
@Override
public int getItemViewType(int position) {
	if (isHeaderPosition(position)) {
		return mHeaderViewInfos.get(position).viewType;

	} else if (isFooterPosition(position)) {
		return mFooterViewInfos.get(position - mHeaderViewInfos.size()
				- mRealAdapter.getItemCount()).viewType;

	} else {
		return mRealAdapter.getItemViewType(position - mHeaderViewInfos.size());
	}
}
这里只需要对头部和尾部做相应处理即可,那么怎么判断是头部或者尾部呢?对头,就是通过该方法传入的position参数来判断的:
private boolean isHeaderPosition(int position) {
	return position < mHeaderViewInfos.size();
}
private boolean isFooterPosition(int position) {
	return position >= mHeaderViewInfos.size() + mRealAdapter.getItemCount();
}

5. 修改onCreateViewHolder()方法
把两个软柿子挑出来搞完之后我们还是要面对比较复杂的onCreateViewHolder(),这里我们也和getItemViewType类似,只有针对头部和尾部处理即可。
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
	if (isHeader(viewType)) {
		int whichHeader = Math.abs(viewType - BASE_HEADER_VIEW_TYPE);
		View headerView = mHeaderViewInfos.get(whichHeader).view;
		return createHeaderFooterViewHolder(headerView);

	} else if (isFooter(viewType)) {
		int whichFooter = Math.abs(viewType - BASE_FOOTER_VIEW_TYPE);
		View footerView = mFooterViewInfos.get(whichFooter).view;
		return createHeaderFooterViewHolder(footerView);

	} else {
		return mRealAdapter.onCreateViewHolder(viewGroup, viewType);
	}
}

这里把创建头部和尾部ViewHolder封装到一个createHeaderFooterViewHolder()方法中:
private RecyclerView.ViewHolder createHeaderFooterViewHolder(View view) {
	if (isStaggeredGrid) {
		StaggeredGridLayoutManager.LayoutParams params = new StaggeredGridLayoutManager.LayoutParams(
				StaggeredGridLayoutManager.LayoutParams.MATCH_PARENT, StaggeredGridLayoutManager.LayoutParams.WRAP_CONTENT);
		params.setFullSpan(true);
		view.setLayoutParams(params);
	}
	return new RecyclerView.ViewHolder(view) {
	};
}

这里为什么判断 isStaggeredGrid我们稍后再讲解,除了这一点外,还是比较简单的,创建了一个Recycler.ViewHolder。

6. 修改onBindViewHolder()方法
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
	if (position < mHeaderViewInfos.size()) {
		// Headers don't need anything special

	} else if (position < mHeaderViewInfos.size() + mRealAdapter.getItemCount()) {
		// This is a real position, not a header or footer. Bind it.
		mRealAdapter.onBindViewHolder(viewHolder, position - mHeaderViewInfos.size());

	} else {
		// Footers don't need anything special
	}
}
我们只需要处理真正的item数据即可,头部和尾部不做处理。

OK,那我们就修改好了,那么来运行一把吧。


什么鬼,怎么添加的头部不是占据一行呢?就是要在我们跳过的createHeaderFooterViewHolder()中isStaggeredGrid来处理的,就是设置如果配置的为StaggeredGridLayoutManager则要占据一行,但是还有一个问题那就是如果是GridLayoutManager也是要占据一行的,这里提供了一个方法来处理占据一行的问题:
/**
 * adjust the LayoutManager SpanSize
 *
 * @param recycler
 * @version 1.0
 */
public void adjustSpanSize(RecyclerView recycler) {
	if(recycler.getLayoutManager() instanceof GridLayoutManager) {
		final GridLayoutManager layoutManager = (GridLayoutManager) recycler.getLayoutManager();
		layoutManager.setSpanSizeLookup(new SpanSizeLookup() {

			@Override
			public int getSpanSize(int position) {
				boolean isHeaderOrFooter =
						isHeaderPosition(position) || isFooterPosition(position);
				return isHeaderOrFooter ? layoutManager.getSpanCount() : 1;
			}

		});
	}

	if(recycler.getLayoutManager() instanceof StaggeredGridLayoutManager) {
		this.isStaggeredGrid = true;
	}
}

这么一来,无论是StaggeredGridLayoutManager还是GridLayoutManager添加的头部和尾部都会占据一行啦。

四、WrapRecyclerView包装类编写


    其实第三步骤,我们就完成了封装,但是为了使用方便,这里再提供一个对RecyclerView的封装,其实比较简单,主要的在setAdapter中,其他的都是对WrapAdapter的简单代理。
@Override
public void setAdapter(Adapter adapter) {
    if(adapter instanceof WrapAdapter) {
        mWrapAdapter = (WrapAdapter) adapter;
        super.setAdapter(adapter);
    } else {
        mWrapAdapter = new WrapAdapter(adapter);
        super.setAdapter(mWrapAdapter);
    }

    if(shouldAdjustSpanSize) {
        mWrapAdapter.adjustSpanSize(this);
    }
}
/**
 * Adds a header view
 *
 * @param view
 * @version 1.0
 */
public void addHeaderView(View view) {
    if (null == view) {
        throw new IllegalArgumentException("the view to add must not be null!");
    } else if(mWrapAdapter == null) {
        throw new IllegalStateException("You must set a adapter before!");
    } else {
        mWrapAdapter.addHeaderView(view);
    }
}
/**
 * Adds a footer view
 *
 * @param view
 * @version 1.0
 */
public void addFooterView(View view) {
    if (null == view) {
        throw new IllegalArgumentException("the view to add must not be null!");
    } else if(mWrapAdapter == null) {
        throw new IllegalStateException("You must set a adapter before!");
    } else {
        mWrapAdapter.addFooterView(view);
    }
}

五、源码及示例

   
给大家提供一个github的地址:WrapRecyclerView
    另外,欢迎 star or f**k me on github!

六、一行引入库

    

如果您的项目使用 Gradle 构建, 只需要在您的build.gradle文件添加下面一行到 dependencies :

compile 'com.kevin:wraprecyclerview:1.0.6'

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值