分析動力
最近在寫自己的項目,使用到RecyclerView這控件替代我之前常用的ListView。使用起來當然感覺比之前的ListView功能強大太多。但是目前RecyclerView卻沒有添加addHeadView()和addFooterView()這兩個列表組件常用的功能。網絡上有很多博客都有介紹他們的方法給RecyclerView添加這兩個功能,主要就是在onCreateViewHolder
和onBindViewHolder
上做文章。作爲一個有追求的Android開發者不能直接照抄他們。也寫出一些自己的想法。所以我就回到ListView的源碼看Google是怎麼實現這功能的
addHeadView()源碼分析
源碼入口
兩個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.
* <p>
* 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();
}
}
}
FixedViewInfo類
是對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;
/** <code>true</code> if the fixed view should be selectable in the list */
public boolean isSelectable;
}
HeaderViewListAdapter的源碼分析
內部對象
類中保存有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<ListView.FixedViewInfo> mHeaderViewInfos;
ArrayList<ListView.FixedViewInfo> 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<ListView.FixedViewInfo>的靜態不可變對象 作用是內存優化 值得學習
static final ArrayList<ListView.FixedViewInfo> EMPTY_INFO_LIST =
new ArrayList<ListView.FixedViewInfo>();
boolean mAreAllFixedViewsSelectable;
private final boolean mIsFilterable;
構造函數
三個參數的構造函數,負責初始化和賦值
public HeaderViewListAdapter(ArrayList<ListView.FixedViewInfo> headerViewInfos,
ArrayList<ListView.FixedViewInfo> 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);
}
添加head和foot視圖的關鍵
在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沒有太大的差別,不是我們分析點。
總結
- 從這幾步的源碼分析,我們可以得知,ListView的addHeadView(),其實就是修改adapter的getView()方法。在使用時包裝HeaderViewListAdapter類有專門處理有head和foot的方法。這種思路和網絡上大多數的博客差不多,應該大家都是從源碼中找到思路的吧。
- 從ListView源碼上得到的思路,其實也可以運用在RecyclerView上,Google官方沒有提供我們就自己動手。當然RecyclerView比ListView要複雜它在MVC的C層分離出 RecyclerView.Adapter和RecyclerView.LayoutManager,分別控制View的顯示內容和佈局位置。通過上面的分析得到的思路接下來我要寫個有addHeadView和addFootView的RecyclerView。
- 這裏有對RecyclerView使用addHeadView的分析整理