Android RecyclerView添加头部和尾部

前言:

在使用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);
    }
}

读者福利

Android架构师的门槛,有没有免费学习资料?

加入Android高级架构群;1007478004,免费提供视频和资料,一起学习,相互讨论。
安卓资料图.png

猜你喜欢

转载自blog.csdn.net/weixin_44941011/article/details/89925227