转载请注明出处 http://blog.csdn.net/u011453163/article/details/58586687
RecyclerView 已经不是一个陌生的组件了,但是相对于现在的项目在还是用比较老旧的ListView,虽然功能都能实现,但是毕竟得跟上时代变化嘛。
关于RecyclerView添加 头部和尾部,网上已经有各种大牛的版本,实现起来也不是很难。但是看一遍不如撸一遍。这里对学习RecyclerView过程中做一些记录,部分实现也有借鉴大牛们的思路,见笑。
先上个简单的效果图。
RecyclerView不像ListView 一样 提供了添加头部和尾部的方法,但是RecyclerView和ListView一样是支持多类型Item布局的,那么反过来 头部和尾部 也就只是一些特定类型的布局而已。所以我们可以通过
public int getItemViewType(int position) 算出头部和尾部的Type 来加载头部和尾部。
这里通过两种方式来实现这个效果,思路是一样的。
自定义一个 ExpandAdapter 继承 RecyclerView.Adapter
public class ExpandAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private RecyclerView.Adapter<RecyclerView.ViewHolder> adapter;
public ExpandAdapter(RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
this.adapter = adapter;
}
@Override
public int getItemViewType(int position) {
return adapter.getItemViewType(position);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return adapter.onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
adapter.onBindViewHolder(holder, position);
}
@Override
public int getItemCount() {
return adapter.getItemCount();
}
}
这里只是简单的做一个转换工作,以方便后期使用不需要去改动太多的东西。
1 通过 List 来实现。
接下来开始分析实现思路,首先要注意的是 public int getItemViewType(int position)默认是返回 0 的,
所以在定义头部和尾部的type是要错开 0 ,网上大部分大牛的思路都是定义一个比较大的起始值。
1 一个较大的起始值
private static final int TPEY_COUNT_START_TAG =100000;
2 两个存储头部和尾部 view的集合,和添加头部和尾部对应的方法
List<View> headViews = new ArrayList<>();
List<View> footViews = new ArrayList<>();
public void addHeadView(View v) {
if (headViews != null) {
v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
headViews.add(v);
}
}
public void addFootView(View v) {
if (footViews != null) {
v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
footViews.add(v);
}
}
3 在 public int getItemViewType(int position) 计算返回的type
public int getItemViewType(int position) {
if (headViews.size() > 0) {
if (position < headViews.size()) {
return position+ TPEY_COUNT_START_TAG;
}
}
if (footViews.size() > 0) {
if (position >= getItemCount() - footViews.size()) {
return position+ TPEY_COUNT_START_TAG;
}
}
return adapter.getItemViewType(position);
}
因为这里只有一个position参数可以使用,所以在position的基础上加个一个较大的值,这里是加法之后我们要做减法来算出下标 取出对应的 头部view和尾部view。
4 从List中取出渲染对应的头部和尾部。
定义头部和尾部对应的ViewHolder 便于理解
class HeaderHolder extends RecyclerView.ViewHolder {
public HeaderHolder(View itemView) {
super(itemView);
}
}
class FootHolder extends RecyclerView.ViewHolder {
public FootHolder(View itemView) {
super(itemView);
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (headViews.size() > 0) {
if (viewType- TPEY_COUNT_START_TAG >= 0 && viewType- TPEY_COUNT_START_TAG < headViews.size()) {
return new HeaderHolder(headViews.get(viewType- TPEY_COUNT_START_TAG));
}
}
if (footViews.size() > 0) {
Log.d("ExpandAdapter", "viewType:" + viewType);
if (viewType- TPEY_COUNT_START_TAG >= getItemCount() - footViews.size() && viewType- TPEY_COUNT_START_TAG < getItemCount()) {
return new FootHolder(footViews.get(viewType- TPEY_COUNT_START_TAG -adapter.getItemCount()-headViews.size()));
}
}
return adapter.onCreateViewHolder(parent, viewType);
}
这里主要就是做一些判断 算出下标 来取出 headViews, footViews中对应的view。
5 绑定数据
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if(holder instanceof FootHolder ||holder instanceof HeaderHolder){
return;
}
adapter.onBindViewHolder(holder, position);
}
@Override
public int getItemCount() {
return adapter.getItemCount() + headViews.size() + footViews.size();
}
动态添加的头部和尾部不需要在适配器里渲染数据,所以直接返回不管。Item总数就是头部和尾部和普通itme的总和,这个没必要说的。
6.由于RecyclerView 的布局形式不同。GridLayoutManager,StaggeredGridLayoutManager,LinearLayoutManager。为了适配这三者,还需要重写两个方法,这个是借鉴网上大牛的。
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder)
{
adapter.onViewAttachedToWindow(holder);
int position = holder.getLayoutPosition();
if ((position >= 0 && position < headViews.size())||(position>=getItemCount()-footViews.size()&&position<getItemCount()))
{
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams)
{
StaggeredGridLayoutManager.LayoutParams p =
(StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(true);
}
}
}
@Override
public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
((GridLayoutManager) recyclerView.getLayoutManager()).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (position >= 0 && position < headViews.size()) {
return ((GridLayoutManager) recyclerView.getLayoutManager()).getSpanCount();
}else if(position>=getItemCount()-footViews.size()&&position<getItemCount()){
return ((GridLayoutManager) recyclerView.getLayoutManager()).getSpanCount();
} else {
return 1;
}
}
});
}
}
StaggeredGridLayoutManager
GridLayoutManager
以上是使用List实现的方式,第二种的思路也是一样的,通过两个HashMap来存储头部和尾部 ,就不详细介绍了,主要列出一些区别。
private static final int TPEY_HEADER_STATRT_INDEX =-100000;
private static final int TPEY_FOOT_START_INDEX =100000;
HashMap<Integer,View> headViewss=new HashMap<>();
HashMap<Integer,View> footViewss=new HashMap<>();
/**添加头部*/
public void addHeadView(View v) {
if(headViewss!=null){
v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
headViewss.put(TPEY_HEADER_STATRT_INDEX+headViewss.size(),v);
}
}
/**添加头部*/
public void addFootView(View v) {
if(footViewss!=null){
v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
footViewss.put(TPEY_FOOT_START_INDEX+footViewss.size(),v);
}
}
/**重写适配器的两个方法也有区别 这里也可以同set 返回key值*/
@Override
public int getItemViewType(int position) {
if(headViewss!=null&&position<headViewss.size()){
return position+TPEY_HEADER_STATRT_INDEX;
}
if(footViewss!=null&&position>=adapter.getItemCount()+headViewss.size()){
return TPEY_FOOT_START_INDEX+(position-adapter.getItemCount()-headViewss.size());
}
return adapter.getItemViewType(position);
}
/**通过Key直接获取*/
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(headViewss.containsKey(viewType)){
return new HeaderHolder(headViewss.get(viewType));
}
if(footViewss.containsKey(viewType)){
return new FootHolder(footViewss.get(viewType));
}
return adapter.onCreateViewHolder(parent, viewType);
}
/**此方法基本一样*/
@Override
public int getItemCount() {
return adapter.getItemCount() + headViewss.size() + footViewss.size();
}
这里因为是使用了ExpandAdapter 来做一个包装,所以相应的使用一个自定义RecyclerView来实现。以便我们不用做太多的改动。
public class ExpandRecyclerView extends RecyclerView{
ExpandAdapter expandAdapter;
public ExpandRecyclerView(Context context) {
super(context);
}
public ExpandRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public ExpandRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void setAdapter(Adapter adapter) {
expandAdapter =new ExpandAdapter(adapter);
super.setAdapter(expandAdapter);
}
public void addHeadView(View v){
if(expandAdapter !=null){
expandAdapter.addHeadView(v);
expandAdapter.notifyDataSetChanged();
}
}
public void addFootView(View v){
if(expandAdapter !=null){
expandAdapter.addFootView(v);
expandAdapter.notifyDataSetChanged();
}
}
}
使用自定义RecyclerView之后 我们不需要去改动适配器 还是该怎么使用就怎么使用。
到此,RecyclerView的添加头部和尾部就结束了。有什么不对的地方望指正。