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:390)
查看源码,发现了出问题的代码:
【注:本文所有源码都是 ListView.java,只是版本不同】
//Android 4.2版本
public boolean removeFooterView(View v) {
if (mFooterViewInfos.size() > 0) {
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版本
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版本
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() > 0|| mFooterViewInfos.size() > 0) {
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