原文連結:https://www.zhangningning.com.cn/blog/Android/Android_listview_addheade.html
一些APP:

他們布局很類似。頂部是搜尋欄 往下依次是輪播,分類 ,活動主題,推薦産品清單,底部Bar。 如果用整個用ScrollView實作的話,實作起來很容易,但是最下面的推薦産品由于是不确定數量的,有可能還挺多,那麼 1.産品清單用ListView,需要解決和ScrollView的沖突及ListView的高度問題 2.産品清單用ScrollView或着LinearLayout,數量多,圖檔多的話,由于沒有緩存重用,有可能有性能問題
換做用ListView來實作,如果不用Header,用itemType來區分的話,要建立N個type,實作起來略顯複雜。
ListView 對Header,Foot而的數量是沒有限制的,用ListView和addHeader處理這種情形是比較合适的(當然頂部搜尋和底部Bar是除外的)。
addHeader,addFooter這兩個方法使用很簡單,就不贅述。主要記錄一下使用的時候可能會遇到的坑。
1.關于Header的隐藏和顯示
ListView 對 Header相關的API共有
listView.getHeaderViewsCount();
listView.addHeaderView(View v);
listView.removeHeaderView(View v);
listView.areHeaderDividersEnabled();
四個函數,沒有控制順序,及是否顯示某個Header的API。 怎麼隐藏一個Header? 如果不再展示可以使用
emoveHeaderView
,再次需要展示呢?就會有順序的問題。 再有想到的是将headerView設為
GONE
,理論上這是對的,但是實際上卻沒有效果,留下了一個空白的占位,也就是說他的parent去正确的響應及布局。現在一般的解決方案是:如果要隐藏一個headerView,就将發的子View的可見性設為
GONE
或者高度設為0。顯示的時候View的可見性設為
VISIBLE
. 最終的解決方案是: 開始的時候将所有的HeaderView都按照順序使用addHeaderView添加到ListView中,通過控制headerView的子View的可見性來控制是否顯示。
2.關于setAdapter和addHeaderView的執行順序的問題
ListView想要添加headerview的話,就要通過
addHeaderView
這個方法,然後想要為ListView設定資料的話,就要調用
setAdapter
方法了。但是,在低版本的裝置(API<=17)上會報
java.lang.IllegalStateException: Cannot add header view to list -- setAdapter has already been called.
的異常。這是因為我們在addHeaderView之前已經調用了
setAdapter
. 通過源碼來看: API-17(Android 4.2)
public void addHeaderView(View v, Object data, boolean isSelectable) {
if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) {
throw new IllegalStateException(
"Cannot add header view to list -- setAdapter has already been called.");
}
FixedViewInfo info = new FixedViewInfo();
info.view = v;
info.data = data;
info.isSelectable = isSelectable;
mHeaderViewInfos.add(info);
// in the case of re-adding a header view, or adding one later on,
// we need to notify the observer
if (mAdapter != null && mDataSetObserver != null) {
mDataSetObserver.onChanged();
}
}
API-18(Android 4.3)
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);
// 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();
}
}
}
可以看到在API<=17中,如果adapter不是空直接抛出異常,而在API>17中,對mAdapter 進行了判斷,如果是空,先把header記錄在mHeaderViewInfos,在setAdapter的時候再将header及adapter封裝在HeaderViewListAdapter中,如果不是空直接建構一個HeaderViewListAdapter。 是以為了保證相容性的問題,在setAdapter之前要把所有的header都加入到ListView裡面。setAdapter之後不要再addHeaderView了。
3.在onItemClickListen中position問題
通過源碼知道,如果一個ListView 存在Header或者Footer,ListView内部會将adapter封裝在一個HeaderViewListAdapter裡面,那麼在onItemClickListen裡面取到是包含header在内的item的位置,已經不是我們一般意義上的資料源的位置。是以實際上
點選的position=取到的position-headerConunt
;當然可以直接這樣做換算,實際上HeaderViewListAdapter裡面已經幫我們做了: HeaderViewListAdapter.java
public Object getItem(int position) {
// Header (negative positions will throw an IndexOutOfBoundsException)
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return mHeaderViewInfos.get(position).data;
}
// Adapter
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getItem(adjPosition);
}
}
// Footer (off-limits positions will throw an IndexOutOfBoundsException)
return mFooterViewInfos.get(adjPosition - adapterCount).data;
}
在HeaderViewListAdapter的
getItem()
中adjPosition就是真正資料源中國的position。 是以在onItemClickListen中取資料的時候使用
parent.getAdapter().getItem(position);
來擷取你真正的實體。(自定義的Adapter要重寫
getItem()
方法,不要傳回null)