当ListView有Header时,onItemClick里的position不正确

今天在做项目的时候,遇到一个问题,记录下来。当给ListView加了一个HeaderView后(代码如下),我们发现,,%20android.view.View,%20int,%20long)” title=”onItemClick”>onItemClick方法里的position参数的值不是我们所期望的,比如点击ListView的第一行,我们期望的position是0,可是实际上却是1,也就是说,它是从Header而不是从第一行开始计数的。

<code class="language-java">@Override</code>

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.home);

mAdapter = new MyAdapter(this);

mListView = (ListView) findViewById(R.id.list);

mListView.addHeaderView(getLayoutInflater().inflate(R.layout.list_header));

mListView.setAdapter(mAdapter);

mListView.setOnClickListener(this);

}

@Override

public void onItemClick(AdapterView<?> parent, View v, int position, long id) {

doSomething(mAdapter.getItem(position));

}

 

Google了下,发现有个老外issue过一个bug,和我遇到的问题一样,不过这个bug被RomainGuy reject掉了,理由是,你用错了,请用getAdapter。这回答的太简洁了,完全没法理解,所以只好又去仔细研究ListView的代码,终于领会他的意思了。把其中addHeaderView和setAdapter方法贴下来

<code class="language-java">/**</code>

* 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改成下面这样,就可以了

<code class="language-java">@Override</code>

public void onItemClick(AdapterView<?> parent, View v, int position, long id) {

doSomething(parent.getAdapter().getItem(position));

}

来源URL:http://blog.chengbo.net/2012/03/09/onitemclick-return-wrong-position-when-listview-has-headerview.html