BaseRecyclerViewAdapterHelper源码解读(二) 添加header和footer
原项目地址: https://github.com/CymChad/BaseRecyclerViewAdapterHelper,首先感谢开源,感谢无私奉献的人们.
首先,我们看到BaseQuickAdapter中醒目的两行代码;
//header footer
private LinearLayout mHeaderLayout;
private LinearLayout mFooterLayout;
这意味着,它是将headerView和footerView添加到了对应LinearLayout中,然后相当于在显示的时候,上面是一个LinearLayout中间是普通item布局,底部是一个LinearLayout(后面也证实了,这个猜想是对的).
header和footer的数量
/**
* if addHeaderView will be return 1, if not will be return 0
* 添加了header则返回1 没有header则返回0
*/
public int getHeaderLayoutCount() {
if (mHeaderLayout == null || mHeaderLayout.getChildCount() == 0) {
return 0;
}
return 1;
}
/**
* if addFooterView will be return 1, if not will be return 0
*/
public int getFooterLayoutCount() {
if (mFooterLayout == null || mFooterLayout.getChildCount() == 0) {
return 0;
}
return 1;
}
/**
* Return root layout of header
*/
public LinearLayout getHeaderLayout() {
return mHeaderLayout;
}
/**
* Return root layout of footer
*/
public LinearLayout getFooterLayout() {
return mFooterLayout;
}
首先可以通过getHeaderLayoutCount()或者getFooterLayoutCount()获取是否添加了footer或者header,添加了就返回1.再通过getHeaderLayout()可以获取header的LinearLayout对象,再通过该对象的getChildCount()即可知道对应子View(也就是header数量)的数量.
getItemCount()
@Override
public int getItemCount() {
int count;
//如果界面上显示了空布局
if (getEmptyViewCount() == 1) {
count = 1;
//如果header可见 则只需要+1就行了,因为header的布局是LinearLayout
if (mHeadAndEmptyEnable && getHeaderLayoutCount() != 0) {
count++;
}
if (mFootAndEmptyEnable && getFooterLayoutCount() != 0) {
count++;
}
} else { //未显示空布局
// 1 or 0 数据项 1 or 0 加载更多 1 or 0
count = getHeaderLayoutCount() + mData.size() + getFooterLayoutCount() + getLoadMoreViewCount();
}
return count;
}
getItemViewType()
@Override
public int getItemViewType(int position) {
if (getEmptyViewCount() == 1) {
boolean header = mHeadAndEmptyEnable && getHeaderLayoutCount() != 0;
switch (position) {
case 0:
if (header) {
return HEADER_VIEW;
} else {
return EMPTY_VIEW;
}
case 1:
if (header) {
return EMPTY_VIEW;
} else {
return FOOTER_VIEW;
}
case 2:
return FOOTER_VIEW;
default:
return EMPTY_VIEW;
}
}
//1 or 0
int numHeaders = getHeaderLayoutCount();
if (position < numHeaders) {
return HEADER_VIEW;
} else {
int adjPosition = position - numHeaders;
int adapterCount = mData.size();
//中间的数据项
if (adjPosition < adapterCount) {
return getDefItemViewType(adjPosition);
} else {
//剩下 footer 加载中布局
adjPosition = adjPosition - adapterCount;
int numFooters = getFooterLayoutCount();
if (adjPosition < numFooters) {
return FOOTER_VIEW;
} else {
return LOADING_VIEW;
}
}
}
}
/**
* 默认的type
* @param position
* @return
*/
protected int getDefItemViewType(int position) {
if (mMultiTypeDelegate != null) {
return mMultiTypeDelegate.getDefItemViewType(mData, position);
}
//其实就是0
return super.getItemViewType(position);
}
这里有空布局的逻辑一起混起的,干脆一起分析,反正不是很难.
- 当有空布局的时候,position的值只可能为0,1,2;
再根据是否显示了header,即可判断出当前position的type应该是什么. - 再看没有显示空布局的情况
- header类型 索引:
< 1
- 中间的数据项类型 需要减去header的数量(1 or 0) ;这里面牵涉到了item多种类型的逻辑,如果是多种类型的则交由mMultiTypeDelegate去处理,如果不是,则默认返回super.getItemViewType(position);
- footer类型 减去header的数量(1 or 0),再减去中间数据项的数量
- 加载中类型 剩下的
- header类型 索引:
onCreateViewHolder()
@Override
public K onCreateViewHolder(ViewGroup parent, int viewType) {
K baseViewHolder = null;
this.mContext = parent.getContext();
this.mLayoutInflater = LayoutInflater.from(mContext);
switch (viewType) {
case LOADING_VIEW:
baseViewHolder = getLoadingView(parent);
break;
case HEADER_VIEW:
baseViewHolder = createBaseViewHolder(mHeaderLayout);
break;
case EMPTY_VIEW:
baseViewHolder = createBaseViewHolder(mEmptyLayout);
break;
case FOOTER_VIEW:
baseViewHolder = createBaseViewHolder(mFooterLayout);
break;
default:
baseViewHolder = onCreateDefViewHolder(parent, viewType);
bindViewClickListener(baseViewHolder);
}
baseViewHolder.setAdapter(this);
return baseViewHolder;
}
/**
* 创建默认的ViewHolder 即中间的数据项的ViewHolder
* @param parent
* @param viewType
* @return
*/
protected K onCreateDefViewHolder(ViewGroup parent, int viewType) {
int layoutId = mLayoutResId;
if (mMultiTypeDelegate != null) { //不同的类型的布局
layoutId = mMultiTypeDelegate.getLayoutId(viewType);
}
return createBaseViewHolder(parent, layoutId);
}
protected K createBaseViewHolder(ViewGroup parent, int layoutResId) {
return createBaseViewHolder(getItemView(layoutResId, parent));
}
/**
* @param layoutResId ID for an XML layout resource to load
* @param parent Optional view to be the parent of the generated hierarchy or else
* simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy
* @return view will be return
*/
protected View getItemView(@LayoutRes int layoutResId, ViewGroup parent) {
return mLayoutInflater.inflate(layoutResId, parent, false);
}
不管是什么类型,最终都会创建BaseViewHolder的对象.
然后通过createBaseViewHolder()方法去创建.我们稍后再来看看createBaseViewHolder()方法.先看看onCreateDefViewHolder()方法.它是数据项创建ViewHolder,比较”特殊”.
如果是默认类型的,则调用onCreateDefViewHolder(),判断是否是多布局,然后走下面的逻辑.
- 多布局->则交给多布局的逻辑去获取layoutId
- 不是多布局,通过注解拿来用
最后需要调用getItemView()方法来创建一个View,
其实就是通过LayoutInflater来创建View,这和我们平时一样嘛,哈哈.
/**
* if you want to use subclass of BaseViewHolder in the adapter,
* you must override the method to create new ViewHolder.
* 如果要在适配器中使用BaseViewHolder的子类,
* *您必须覆盖该方法才能创建新的ViewHolder。
*
* @param view view
* @return new ViewHolder
*/
@SuppressWarnings("unchecked")
protected K createBaseViewHolder(View view) {
Class temp = getClass();
Class z = null;
while (z == null && null != temp) {
//判断z是否是BaseViewHolder的子类或接口 不是则返回null
z = getInstancedGenericKClass(temp);
//返回超类
temp = temp.getSuperclass();
}
K k;
// 泛型擦除会导致z为null
if (z == null) {
//为null则说明z不是BaseViewHolder的子类或接口 则创建一个BaseViewHolder
k = (K) new BaseViewHolder(view);
} else {
//尝试创建z的实例 利用反射
k = createGenericKInstance(z, view);
}
return k != null ? k : (K) new BaseViewHolder(view);
}
/**
* get generic parameter K
* 判断z是否是BaseViewHolder的子类或接口
*
* @param z
* @return
*/
private Class getInstancedGenericKClass(Class z) {
//getGenericSuperclass()获得带有泛型的父类
//Type是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。
Type type = z.getGenericSuperclass();
if (type instanceof ParameterizedType) {
Type[] types = ((ParameterizedType) type).getActualTypeArguments();
for (Type temp : types) {
//判断tempClass是否是BaseViewHolder类型相同或具有相同的接口
if (temp instanceof Class) {
Class tempClass = (Class) temp;
if (BaseViewHolder.class.isAssignableFrom(tempClass)) {
return tempClass;
}
} else if (temp instanceof ParameterizedType) {
Type rawType = ((ParameterizedType) temp).getRawType();
if (rawType instanceof Class && BaseViewHolder.class.isAssignableFrom(
(Class<?>) rawType)) {
return (Class<?>) rawType;
}
}
}
}
return null;
}
/**
* try to create Generic K instance
* 尝试创建Generic K实例
*
* @param z
* @param view
* @return
*/
@SuppressWarnings("unchecked")
private K createGenericKInstance(Class z, View view) {
try {
Constructor constructor;
// inner and unstatic class
//成员类&&非静态类
if (z.isMemberClass() && !Modifier.isStatic(z.getModifiers())) {
//获取z的构造函数
constructor = z.getDeclaredConstructor(getClass(), View.class);
//禁止java语言访问检查
constructor.setAccessible(true);
//通过构造方法构造z对象
return (K) constructor.newInstance(this, view);
} else {
constructor = z.getDeclaredConstructor(View.class);
constructor.setAccessible(true);
return (K) constructor.newInstance(view);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
其实这一大段代码其实是在创建BaseViewHolder的实例,首先是判断是否是BaseViewHolder的子类,如果不是则直接创建BaseViewHolder;如果是则通过反射去创建BaseViewHolder实例.
添加header
/**
* Append header to the rear of the mHeaderLayout.
* 默认添加header到headerLayout的底部(索引最大的那个位置)
*
* @param header
*/
public int addHeaderView(View header) {
return addHeaderView(header, -1);
}
/**
* Add header view to mHeaderLayout and set header view position in mHeaderLayout.
* When index = -1 or index >= child count in mHeaderLayout,
* the effect of this method is the same as that of {@link #addHeaderView(View)}.
*
* @param header
* @param index the position in mHeaderLayout of this header.
* When index = -1 or index >= child count in mHeaderLayout,
* the effect of this method is the same as that of {@link #addHeaderView(View)}.
*/
public int addHeaderView(View header, int index) {
return addHeaderView(header, index, LinearLayout.VERTICAL);
}
/**
* @param header
* @param index
* @param orientation
*/
public int addHeaderView(View header, int index, int orientation) {
// 如果为空 则创建头布局
if (mHeaderLayout == null) {
mHeaderLayout = new LinearLayout(header.getContext());
// 方向 LayoutParams设置
if (orientation == LinearLayout.VERTICAL) {
mHeaderLayout.setOrientation(LinearLayout.VERTICAL);
mHeaderLayout.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
} else {
mHeaderLayout.setOrientation(LinearLayout.HORIZONTAL);
mHeaderLayout.setLayoutParams(new LayoutParams(WRAP_CONTENT, MATCH_PARENT));
}
}
final int childCount = mHeaderLayout.getChildCount();
// 如果index不合法,则添加到索引最大的位置
if (index < 0 || index > childCount) {
index = childCount;
}
mHeaderLayout.addView(header, index); //就是添加到LinearLayout中嘛,哈哈,被我猜中啦
/*
如果头布局(LinearLayout)中子View(header的item)的数量等于1
说明这是第一次添加headerLayout进RecyclerView
需要进行通知刷新操作 告知RecyclerView第一个索引处更新啦
*/
if (mHeaderLayout.getChildCount() == 1) {
int position = getHeaderViewPosition();
if (position != -1) {
notifyItemInserted(position); //告知RecyclerView第一个索引处更新啦
// 这时RecyclerView中的第一项已经是headerLayout(LinearLayout)了
}
}
return index;
}
public int setHeaderView(View header) {
return setHeaderView(header, 0, LinearLayout.VERTICAL);
}
public int setHeaderView(View header, int index) {
return setHeaderView(header, index, LinearLayout.VERTICAL);
}
public int setHeaderView(View header, int index, int orientation) {
if (mHeaderLayout == null || mHeaderLayout.getChildCount() <= index) {
return addHeaderView(header, index, orientation);
} else {
mHeaderLayout.removeViewAt(index);
mHeaderLayout.addView(header, index);
return index;
}
}
别被吓到了,这次的代码有点多,我们一个一个来分析.添加headrView最终都会到达addHeaderView(View header, int index, int orientation)方法,所以我们直接来分析该方法即可.
1.首先是判断是否初始化了mHeaderLayout(header的布局容器LinearLayout,所有的headerView都放在这里的),没有初始化则创建mHeaderLayout
- 设置LinearLayout方向
- 设置LayoutParams
2.判断headerView的插入位置是否合法
- 合法:插入到应该在的位置
- 不合法:插入到索引最大的位置
3.判断是否是第一次添加headerView
- 如果头布局(LinearLayout)中子View(header的item)的数量等于1,
说明这是第一次添加headerLayout进RecyclerView;
需要进行通知刷新操作,告知RecyclerView第一个索引处更新啦; - 如若不是第一次添加headerView,则添加到LinearLayout中相应位置就不用管了,因为整个LinearLayout在RecyclerView只占用一个位置,不用通知RecyclerView进行刷新操作.
4.后面的setXXX()方法,其实没什么用吧,差不多就是对前面方法改个名字.
添加footer
/**
* Append footer to the rear of the mFooterLayout.
*
* @param footer
*/
public int addFooterView(View footer) {
return addFooterView(footer, -1, LinearLayout.VERTICAL);
}
public int addFooterView(View footer, int index) {
return addFooterView(footer, index, LinearLayout.VERTICAL);
}
/**
* Add footer view to mFooterLayout and set footer view position in mFooterLayout.
* When index = -1 or index >= child count in mFooterLayout,
* the effect of this method is the same as that of {@link #addFooterView(View)}.
*
* @param footer
* @param index the position in mFooterLayout of this footer.
* When index = -1 or index >= child count in mFooterLayout,
* the effect of this method is the same as that of {@link #addFooterView(View)}.
*/
public int addFooterView(View footer, int index, int orientation) {
if (mFooterLayout == null) {
mFooterLayout = new LinearLayout(footer.getContext());
if (orientation == LinearLayout.VERTICAL) {
mFooterLayout.setOrientation(LinearLayout.VERTICAL);
mFooterLayout.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
} else {
mFooterLayout.setOrientation(LinearLayout.HORIZONTAL);
mFooterLayout.setLayoutParams(new LayoutParams(WRAP_CONTENT, MATCH_PARENT));
}
}
final int childCount = mFooterLayout.getChildCount();
if (index < 0 || index > childCount) {
index = childCount;
}
mFooterLayout.addView(footer, index);
if (mFooterLayout.getChildCount() == 1) {
int position = getFooterViewPosition();
if (position != -1) {
notifyItemInserted(position);
}
}
return index;
}
public int setFooterView(View header) {
return setFooterView(header, 0, LinearLayout.VERTICAL);
}
public int setFooterView(View header, int index) {
return setFooterView(header, index, LinearLayout.VERTICAL);
}
public int setFooterView(View header, int index, int orientation) {
if (mFooterLayout == null || mFooterLayout.getChildCount() <= index) {
return addFooterView(header, index, orientation);
} else {
mFooterLayout.removeViewAt(index);
mFooterLayout.addView(header, index);
return index;
}
}
这里的代码和前面添加header的逻辑一样的嘛,这里就不多说了.
移除header和footer
/**
* remove header view from mHeaderLayout.
* When the child count of mHeaderLayout is 0, mHeaderLayout will be set to null.
*
* @param header
*/
public void removeHeaderView(View header) {
if (getHeaderLayoutCount() == 0) return;
mHeaderLayout.removeView(header);
//如果mHeaderLayout已经没有子View,则直接将mHeaderLayout从RecyclerView中移除
if (mHeaderLayout.getChildCount() == 0) {
int position = getHeaderViewPosition();
if (position != -1) {
notifyItemRemoved(position);
}
}
}
/**
* remove footer view from mFooterLayout,
* When the child count of mFooterLayout is 0, mFooterLayout will be set to null.
*
* @param footer
*/
public void removeFooterView(View footer) {
if (getFooterLayoutCount() == 0) return;
mFooterLayout.removeView(footer);
//如果mFooterLayout已经没有子View,则直接将mHeaderLayout从RecyclerView中移除
if (mFooterLayout.getChildCount() == 0) {
int position = getFooterViewPosition();
if (position != -1) {
notifyItemRemoved(position);
}
}
}
/**
* remove all header view from mHeaderLayout and set null to mHeaderLayout
* 移除所有header view
*/
public void removeAllHeaderView() {
if (getHeaderLayoutCount() == 0) return;
mHeaderLayout.removeAllViews();
int position = getHeaderViewPosition();
if (position != -1) {
notifyItemRemoved(position);
}
}
/**
* remove all footer view from mFooterLayout and set null to mFooterLayout
* 移除所有footer view
*/
public void removeAllFooterView() {
if (getFooterLayoutCount() == 0) return;
mFooterLayout.removeAllViews();
int position = getFooterViewPosition();
if (position != -1) {
notifyItemRemoved(position);
}
}
/**
* 返回HeaderView在RecyclerView中的位置
* @return 0 or -1
*/
private int getHeaderViewPosition() {
//Return to header view notify position
if (getEmptyViewCount() == 1) {
//有空布局 并且 头布局可见
if (mHeadAndEmptyEnable) {
return 0;
}
} else {
//没有空布局 返回0
return 0;
}
return -1;
}
private int getFooterViewPosition() {
//Return to footer view notify position
if (getEmptyViewCount() == 1) {
int position = 1;
if (mHeadAndEmptyEnable && getHeaderLayoutCount() != 0) {
//空布局可见 并且 头布局可见
position++;
}
if (mFootAndEmptyEnable) {
//尾布局可见
return position;
}
} else {
//头布局有无:0 or 1 正常项的大小
return getHeaderLayoutCount() + mData.size();
}
return -1;
}
这里的逻辑相对比较简单,就是将header(footer) view从mHeaderLayout(mFooterLayout)中移除,如果全部都移出去了,则通知RecyclerView刷新.
这里最后2个方法是用于返回headerLayout和footerLayout在RecyclerView中的位置,在计算位置的时候需要加入对空布局的判断.
总结
好了,差不多就这些了,如果有不足的,之后再来补充.其实花点时间看源码还是挺有趣的嘛,感觉整个人神清气爽啊,哈哈.最开始我有点心理恐惧,怕看不懂,现在好一些了,看源码需要克服心理恐惧.年轻人,不要怕,就是干.看开源项目的源码,有很多好处,我就不一一列举了,反正就是好.