前言
这次主要关于RecyclerView
添加header
和footer
的实现方法,我们都知道,在使用ListView
的时候我们能自由的给自己的ListView
添加头部与尾部。使用addHeaderView()
与addFooterView()
方法实现。只要自己自定义布局文件,添加进去即可。但当我们使用RecyclerView
的时候就会发现这两个方便的方法没有了。那么如果此时我们要实现这些功能时要怎么办呢?不急,下面我们先来熟悉下ListView
的添加header
与footer
的实现原理。
ListView源码
既然ListView
实现了添加头部与尾部的原理,所以我们可以先进入ListView
的源码,查看其中的addHeaderView()
方法与addFooterView()
方法。
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();
}
}
}
这里省略addFooterView()源码,其实跟addHeaderView()基本一致
通过addHeaderView()
的源码我们可以发现,它是使用一个FixedViewInfo
类来存储数据。
public class FixedViewInfo {
/** The view to add to the list */
public View view;
/** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
public Object data;
/** true
if the fixed view should be selectable in the list */
public boolean isSelectable;
}
其中最主要的代码就是
if (!(mAdapter instanceof HeaderViewListAdapter)) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}
我们会发现它new
了一个HeaderViewListAdapter
类,那么我们再进入HeaderViewListAdapter
源码中,发现他就相对于一个我们自定义的Adapter
对header
与footer
的封装。
代码都比较简单,主要是思想,其中主要的是有
public int getCount() {
if (mAdapter != null) {
return getFootersCount() + getHeadersCount() + mAdapter.getCount();
} else {
return getFootersCount() + getHeadersCount();
}
}
根据position
来设置ViewType
的值
public int getItemViewType(int position) {
int numHeaders = getHeadersCount();
if (mAdapter != null && position >= numHeaders) {
int adjPosition = position - numHeaders;
int adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getItemViewType(adjPosition);
}
}
return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
}
再看getView()
方法
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;
}
与getItemViewType
类似,当position
小于header
的count
时,此时返回头部View
,当position
减去headeer
的count
时依然小于正常的adapter
的count
,此时返回正常的View
,如果还有,自然剩下的就是footer
了。
所以根据ListView
的添加头部与尾部的实现原理,我们可以模仿实现RecyclerView
的相同的功能。
EnhanceRecyclerView
那么我们根据ListView
来实现一个能够添加header
与footer
的RecyclerView
,我这里定义为EnhanceRecyclerView
FixedViewInfo
在上面我们看到ListView
中使用到了FixedViewInfo
,就是一个自定义的类,用来存储数据,我们做适当的修改如下,View
还是不变,依然是添加的视图,将其余的删掉,换成viewType
。
public class FixedViewInfo {
public View view;
public int viewType;
}
addHeaderView与addFooterView
做相应的修改,其中BASE_HEADER_VIEW_TYPE
与BASE_FOOTER_VIEW_TYPE
是一个final
的int
数据
public void addHeaderView(View view) {
FixedViewInfo info = new FixedViewInfo();
info.view = view;
info.viewType = BASE_HEADER_VIEW_TYPE + mHeaderViewInfos.size();
mHeaderViewInfos.add(info);
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
public void addFooterView(View view) {
FixedViewInfo info = new FixedViewInfo();
info.view = view;
info.viewType = BASE_FOOTER_VIEW_TYPE + mFooterViewInfos.size();
mFooterViewInfos.add(info);
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
setAdapter
下面是setAdapter()
,使用后面封装的WrapperRecyclerViewAdapter
@Override
public void setAdapter(Adapter adapter) {
if (!(adapter instanceof WrapperRecyclerViewAdapter))
mAdapter = new WrapperRecyclerViewAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
super.setAdapter(mAdapter);
// if (isShouldSpan) {
// ((WrapperRecyclerViewAdapter) mAdapter).adjustSpanSize(this);
// }
}
WrapperRecyclerViewAdapter
我们也可以根据ListView
的HeaderViewListAdapter
来模仿实现一个封装头部与尾部的adapter
。我这里定义为WrapperRecyclerViewAdapter
继承RecyclerView.Adapter
根据情况适当修改。
getItemCount
@Override
public int getItemCount() {
if (mAdapter != null) {
return getHeadersCount() + getFootersCount() + mAdapter.getItemCount();
} else {
return getHeadersCount() + getFootersCount();
}
}
getItemViewType
@Override
public int getItemViewType(int position) {
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return mHeaderViewInfos.get(position).viewType;
}
int adjPosition = position - numHeaders;
int adapterPosition = 0;
if (mAdapter != null) {
adapterPosition = mAdapter.getItemCount();
if (adjPosition < adapterPosition) {
return mAdapter.getItemViewType(adjPosition);
}
}
return mFooterViewInfos.get(position - adapterPosition - getHeadersCount()).viewType;
}
这里逻辑与ListView
的基本一致。
onCreateViewHolder
这里需将HeaderViewListAdapter
中的getView()
方法分成两部分来实现。
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType >= EnhanceRecyclerView.BASE_HEADER_VIEW_TYPE && viewType < EnhanceRecyclerView.BASE_HEADER_VIEW_TYPE + getHeadersCount()) {
View view = mHeaderViewInfos.get(viewType - EnhanceRecyclerView.BASE_HEADER_VIEW_TYPE).view;
return viewHolder(view);
} else if (viewType >= EnhanceRecyclerView.BASE_FOOTER_VIEW_TYPE && viewType < EnhanceRecyclerView.BASE_FOOTER_VIEW_TYPE + getFootersCount()) {
View view = mFooterViewInfos.get(viewType - EnhanceRecyclerView.BASE_FOOTER_VIEW_TYPE).view;
return viewHolder(view);
}
return mAdapter.onCreateViewHolder(parent, viewType);
}
根据不同的viewType
来实现不同的布局文件.不是header
与footer
布局时则直接回调原始adapter
的onCreateViewHolder()
方法。实现正常的布局。
onBindViewHolder
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return;
}
int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getItemCount();
if (adjPosition < adapterCount) {
mAdapter.onBindViewHolder(holder, adjPosition);
return;
}
}
}
根据position
的位置来选择,当position
属于正常的位置范围之内时,则回调原始的adapter
的onBindViewHolder()
方法,实现数据的绑定;对于其它的情况,无需做处理。
主要的模仿变动就是这些,然后自己在根据情况进行适当的修改,最后在布局文件中使用自定义的EnhanceRecyclerView
替代RecyclerView
调整
我们都知道在使用RecyclerView
的时候要设置LayoutManager
,如果按照上面的实现,当LayoutManager
设置为LinearLayout
时,自然没上面问题,头部与尾部能正常添加;但当我们的LayoutManager
设置为GridLayoutManager
与StaggeredGridLayoutManager
时,我们会发现头部与尾部的添加出现异常,就是他们不能独自占一行,所以这里我们要做相应的调整,使他们在添加头部与尾部的时候独自占一行。
adjustSpanSize
在上面我们自定义的WrapperRecyclerViewAdapter
中添加adjustSpanSize()
方法,用来实现调整。
public void adjustSpanSize(RecyclerView recyclerView) {
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
final GridLayoutManager manager = (GridLayoutManager) recyclerView.getLayoutManager();
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int numHeaders = getHeadersCount();
int adjPosition = position - numHeaders;
if (position < numHeaders || adjPosition >= mAdapter.getItemCount())
return manager.getSpanCount();
return 1;
}
});
}
if (recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {
isStaggered = true;
}
}
可以看出当LayoutManager
为GridLayoutManager
时,我们通过manager
来设置setSpanSizeLookup()
在getSpanSize()
方法中根据具体判断来调用 manager.getSpanCount()
来实现头部与尾部的占一行的效果;对于StaggeredGridLayoutManager
因为其没有setSpanSizeLookup()
方法,所以我们先做标记,在ViewHolder
中为每一个需要的ItemView
来设置params
,setFullSpa(true)
方法来填充一行。
private RecyclerView.ViewHolder viewHolder(View itemView) {
if (isStaggered) {
StaggeredGridLayoutManager.LayoutParams params = new StaggeredGridLayoutManager.LayoutParams(StaggeredGridLayoutManager.LayoutParams.MATCH_PARENT,
StaggeredGridLayoutManager.LayoutParams.WRAP_CONTENT);
params.setFullSpan(true);
itemView.setLayoutParams(params);
}
return new RecyclerView.ViewHolder(itemView) {
};
}
setLayoutManager
在EnhanceRecyclerView
中重写setLayoutManager()
方法,添加标记。
@Override
public void setLayoutManager(LayoutManager layout) {
if (layout instanceof GridLayoutManager || layout instanceof StaggeredGridLayoutManager)
isShouldSpan = true;
super.setLayoutManager(layout);
}
最后再 在EnhanceRecyclerView
的setAdapter()
方法中根据标记来判断是否调用adjustSpanSize()
方法来进行调整header
与footer
.
if (isShouldSpan) {
((WrapperRecyclerViewAdapter) mAdapter).adjustSpanSize(this);
}
其实就是前面setAdapter()
中所注释的代码。
这样整个调整部分就基本完成。header
与footer
的添加也就基本完成了。
总结
通过RecyclerView
的头部与尾部的添加,我们应该能学习如何运用原有的资源来实现我们所需要的功能。虽然方法不一定是最好的,因为使用这种方法实现也存在一些问题,例如不能在初始化(onCreate)中同时添加header
与footer
,否则会出现布局混乱的现象,现在还不知道为何有这种特例,希望知道的可以告知解决下,但这种模仿的思想还是很值得推荐的。
个人blog地址:https://idisfkj.github.io/arc...