最近在项目里面有为listView添加头信息的需求,但是添加之后发现了一个有趣的问题,就是添加之后,在onitemCLick里面的position发生错乱了,首先想到的是直接手动+1,但是这种处理方法容易引起误解,于是Google了一下,
发现有个老外issue过一个bug,和我遇到的问题一样,不过这个bug被RomainGuy reject掉了,理由是你用错了,请用getAdapter,这回答的太简洁了,完全没法理解,所以只好又去仔细研究ListView的代码,终于领会他的意思了。把其中addHeaderView和setAdapter方法贴下来
/**
* Add a fixed view to appear at the top of the list. If addHeaderView 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: Call this before calling setAdapter. This is so ListView can wrap
* the supplied cursor with one that that will also account for header
* views.
*
* @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) {
if (mAdapter != null) {
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);
}
/**
* Sets the data behind this ListView.
*
* The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
* depending on the ListView features currently in use. For instance, adding
* headers and/or footers will cause the adapter to be wrapped.
*
* @param adapter The ListAdapter which is responsible for maintaining the
* data backing this list and for producing a view to represent an
* item in that data set.
*
* @see #getAdapter()
*/
@Override
public void setAdapter(ListAdapter adapter) {
if (null != mAdapter) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
resetList();
mRecycler.clear();
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
//其它的一些代码这里省略之...
}
从代码和注释里都可以很清楚的得知,addHeaderView一定要在setAdapter之前调用,如果不这样做,addHeaderView会抛出一个异常。Android为什么要这样?这是因为,在setAdapter的时候,会针对我遇到的这种情况(也就是添加Header后position不正确的这种情况)做些特殊的处理。setAdapter在内部判断了当前ListView是否有Header或者Footer,如果没有,就直接使用参数传进来的adapter;如果有,则用一个decorated的HeaderViewListAdapter来替换参数。这个HeaderViewListAdapter的使命,就是排除Header和Footer,让position(当然也包括getItem, getItemId)等方法的position参数)正确返回。
分析到这里,解决方案基本就出来了:在onItemClick不要直接使用我们声明的adapter,而是使用ListView里的那个decorated adapter。获取它的方法就是调用parent.getAdapter()。当然,如果ListView没有Header和Footer,直接使用声明的adapter也没有问题,不过为了避免出错,还是统一使用decorated adapter比较好。
把我们的onItemClick改成下面这样,就可以了
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
doSomething(parent.getAdapter().getItem(position));
}