最近在写自己的项目,使用到RecyclerView这控件替代我之前常用的ListView。使用起来当然感觉比之前的ListView功能强大太多。但是目前RecyclerView却没有添加addHeadView()和addFooterView()这两个列表组件常用的功能。网络上有很多博客都有介绍他们的方法给RecyclerView添加这两个功能,主要就是在onCreateViewHolder
和onBindViewHolder
上做文章。作为一个有追求的Android开发者不能直接照抄他们。也写出一些自己的想法。所以我就回到ListView的源码看Google是怎么实现这功能的
两个add重载方法最后来到这里,先看英文注释
/**
* Add a fixed view to appear at the top of the list. If this method is
* called more than once, the views will appear in the order they were
* added. Views added using this call can take focus if they want.
*
* Note: When first introduced, this method could only be called before
* setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
* {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
* called at any time. If the ListView's adapter does not extend
* {@link HeaderViewListAdapter}, it will be wrapped with a supporting
* instance of {@link WrapperListAdapter}.
*
* @param v The view to add.
* @param data Data to associate with this view
* @param isSelectable whether the item is selectable
*/
public void addHeaderView(View v, Object data, boolean isSelectable) {}
先看方法的注释部分,英文大概意思是:这是给adapter添加一个顶级视图的方法,有使用提示和参数说明。
ListView采用MVC模式 模型(model)-视图(view)-控制器(controller)
ListView负责UI显示是View层
构造Adapter时传入的Data数据是Model层,是数据的来源。
Adapter适配器负责控制View层需要显示什么样的UI,是最关键的Controller层。
所以给ListView添加head其实就是在adapter上做修改。note:提示这个方法要在绑定适配器setAdapter()
之前调用。
再看里面的源码,代码不多。主要分三步操作
public void addHeaderView(View v, Object data, boolean isSelectable) {
//第一步 数据的初始化和赋值 并添加到头部视图list中
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) {
//第二步 关键点
//给mAdapter赋值成HeaderViewListAdapter对象 并把head、foot视图列表和原来的adapter传进去
//所以这里就是关键 从这里进入看HeaderViewListAdapter类的源码
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();
}
}
}
是对HeadView视图的包装,里面封装了三个字段。
/**
* 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 {@link ListAdapter#getItem(int)}. */
public Object data;
/** true
if the fixed view should be selectable in the list */
public boolean isSelectable;
}
类中保存有3个关键内部对象
public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {
//关键对象-->开始
private final ListAdapter mAdapter;
// These two ArrayList are assumed to NOT be null.
// They are indeed created when declared in ListView and then shared.
ArrayList mHeaderViewInfos;
ArrayList mFooterViewInfos;
//关键对象-->结束
// Used as a placeholder in case the provided info views are indeed null.
// Currently only used by some CTS tests, which may be removed.
//处理空 ArrayList的静态不可变对象 作用是内存优化 值得学习
static final ArrayList EMPTY_INFO_LIST =
new ArrayList();
boolean mAreAllFixedViewsSelectable;
private final boolean mIsFilterable;
三个参数的构造函数,负责初始化和赋值
public HeaderViewListAdapter(ArrayList headerViewInfos,
ArrayList footerViewInfos,
ListAdapter adapter) {
mAdapter = adapter;
mIsFilterable = adapter instanceof Filterable;
if (headerViewInfos == null) {
//如果为null 内部对象被静态不可变量赋值 避免new出新对象和非空处理 值得学习
mHeaderViewInfos = EMPTY_INFO_LIST;
} else {
mHeaderViewInfos = headerViewInfos;
}
if (footerViewInfos == null) {
mFooterViewInfos = EMPTY_INFO_LIST;
} else {
mFooterViewInfos = footerViewInfos;
}
mAreAllFixedViewsSelectable =
areAllListInfosSelectable(mHeaderViewInfos)
&& areAllListInfosSelectable(mFooterViewInfos);
}
在getView上做修改
public View getView(int position, View convertView, ViewGroup parent) {
// Header (negative positions will throw an IndexOutOfBoundsException)
int numHeaders = getHeadersCount();
//从position从0开始取view 先进入这里从FixedViewInfo对象中直接取出View对象作为返回值
if (position < numHeaders) {
return mHeaderViewInfos.get(position).view;
}
//普通的itemView视图 从传入的mAdapter的getView()方法中取 这步一般是我们自己写的BaseAdapter.getView(),就是我们重写的部分,由我们的代码控制
// Adapter
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getView(adjPosition, convertView, parent);
}
}
//foot视图同Head原理
// Footer (off-limits positions will throw an IndexOutOfBoundsException)
return mFooterViewInfos.get(adjPosition - adapterCount).view;
}
和一般的adapter没有太大的差别,不是我们分析点。