天天看點

Android ListView ClassCastException

前言

8012年了,在Android開發中,還是避免不了使用 ListView,通過

addFooterView

去添加底部視圖,UI重新整理時,又通過

removeFooterView

去移除舊的視圖,在Android 4.3版本及以下

removeFooterView

時,發生閃退,日志如下:

com.company.adapters.MyAdapter cannot be cast to android.widget.HeaderViewListAdapter
at android.widget.ListView.removeFooterView(ListView.java:)
           

問題原因

檢視源碼,發現了出問題的代碼:

【注:本文所有源碼都是 ListView.java,隻是版本不同】

//Android 4.2版本
public boolean removeFooterView(View v) {
    if (mFooterViewInfos.size() > ) {
        boolean result = false;
        if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
            if (mDataSetObserver != null) {
                mDataSetObserver.onChanged();
            }
            result = true;
        }
        removeFixedViewInfo(v, mFooterViewInfos);
        return result;
    }
    return false;
}
           

直接強轉 mAdapter 為

HeaderViewListAdapter

,顯然這裡是異常的根源,此時的 mAdapter 根本不是

HeaderViewListAdapter

類型。

那麼為什麼 4.4及以上是可以的呢,我們對比代碼,發現4.3及以下的

addFooterView

和4.4及以上的有些許不同:

Android 4.3及以下版本

//Android 4.3版本
public void addFooterView(View v, Object data, boolean isSelectable) {
    FixedViewInfo info = new FixedViewInfo();
    info.view = v;
    info.data = data;
    info.isSelectable = isSelectable;
    mFooterViewInfos.add(info);
    if (mAdapter != null && mDataSetObserver != null) {
        mDataSetObserver.onChanged();
    }
}
           

Android 4.4及以上版本

//Android 4.4版本
public void addFooterView(View v, Object data, boolean isSelectable) {
    final FixedViewInfo info = new FixedViewInfo();
    info.view = v;
    info.data = data;
    info.isSelectable = isSelectable;
    mFooterViewInfos.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 footer view, or adding one later on,
        // we need to notify the observer.
        if (mDataSetObserver != null) {
            mDataSetObserver.onChanged();
        }
    }
}
           

可以看到,4.4版本判斷如果 mAdapter 不為空,則在 mAdapter 外包一層

HeaderViewListAdapter

,這樣 mAdapter 的類型永遠都是

HeaderViewListAdapter

,是以不會出現

ClassCastException

解決方案

在研究過 ListView 代碼後,我們發現,在 Android 4.2 的

addFooterView

的注釋上,有這麼一句:“在調用 setAdapter 之前先調用 addFooterView ”

/**
 * NOTE: Call this before calling setAdapter. This is so ListView can wrap
 * the supplied cursor with one that will also account for header and footer
 * views.
 */
public void addFooterView(View v, Object data, boolean isSelectable) { //...}
           

為什麼呢?我們繼續看看在

setAdapter

方法中怎麼寫的:

@Override
public void setAdapter(ListAdapter adapter) {
    if (mAdapter != null && mDataSetObserver != null) {
        mAdapter.unregisterDataSetObserver(mDataSetObserver);
    }
    resetList();
    mRecycler.clear();
    // size大于0,說明有 headerView/footerView
    if (mHeaderViewInfos.size() > || mFooterViewInfos.size() > ) {
        mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
    } else {
        mAdapter = adapter;
    }

    mOldSelectedPosition = INVALID_POSITION;
    mOldSelectedRowId = INVALID_ROW_ID;
    // AbsListView#setAdapter will update choice mode states.
    super.setAdapter(adapter);
    //...略
}
           

原來在

setAdapter

中,如果發現 ListView 中有 HeaderView/FooterView ,則将 mAdapter 包一層

HeaderViewListAdapter

,也就是說,如果我們在

setAdapter

之前就調用 addFooterView/addHeaderView ,再

setAdapter

,這樣就不會報錯了。

先調用 addFooterView ,會執行

mFooterViewInfos.add(info);

将 footerView 的相關資訊加進去,這樣size>0,後面再 setAdapter 時,就會包一層

HeaderViewListAdapter

,不會出現類型轉換異常。

結論

在Android 4.3及以下版本,在使用 ListView 時,如果要添加 headerView/FooterView ,順序一定是:

mListView.addFooterView(footerView);
mListView.setAdapter(mAdapter);
mListView.removeFooterView(footerView);
           

這樣就能相容 4.3及以下版本。

如果隻針對Android 4.4 以上版本,則無需考慮順序。總的來說,先 addFooterView 再 setAdapter 最保險。

參考資料

https://stackoverflow.com/questions/24700588/i-am-using-the-listview-add-remove-footer-for-listview-cross-app-in-android-vers

繼續閱讀