天天看點

Android 在ListView使用addHeader注意的問題

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

一些APP:

Android 在ListView使用addHeader注意的問題

他們布局很類似。頂部是搜尋欄 往下依次是輪播,分類 ,活動主題,推薦産品清單,底部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)