当ListView有Header时,onItemClick里的position不正确
做毕设时遇到这个问题,恰好网上有类似情况,记录一下,转自http://www.cnblogs.com/tt_mc/p/3618000.html,谢谢作者。
当给ListView加了一个HeaderView后(代码如下),我们发现,onItemClick方法里的position
参数的值不是我们所期望的,比如点击ListView的第一行,我们期望的position
是0,可是实际上却是1,也就是说,它是从Header而不是从第一行开始计数的。
1 @Override 2 protected void onCreate(Bundle savedInstanceState) { 3 super.onCreate(savedInstanceState); 4 5 setContentView(R.layout.home); 6 7 mAdapter = new MyAdapter(this); 8 9 mListView = (ListView) findViewById(R.id.list); 10 mListView.addHeaderView(getLayoutInflater().inflate(R.layout.list_header)); 11 mListView.setAdapter(mAdapter); 12 mListView.setOnClickListener(this); 13 } 14 15 @Override 16 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 17 doSomething(mAdapter.getItem(position)); 18 }
Google了下,发现有个老外issue过一个bug,和我遇到的问题一样,不过这个bug被RomainGuy reject掉了,理由是,你用错了,请用getAdapter。这回答的太简洁了,完全没法理解,所以只好又去仔细研究ListView的代码,终于领会他的意思了。把其中addHeaderView和setAdapter方法贴下来。
1 /** 2 * Add a fixed view to appear at the top of the list. If addHeaderView is 3 * called more than once, the views will appear in the order they were 4 * added. Views added using this call can take focus if they want. 5 * <p> 6 * NOTE: Call this before calling setAdapter. This is so ListView can wrap 7 * the supplied cursor with one that that will also account for header 8 * views. 9 * 10 * @param v The view to add. 11 * @param data Data to associate with this view 12 * @param isSelectable whether the item is selectable 13 */ 14 public void addHeaderView(View v, Object data, boolean isSelectable) { 15 if (mAdapter != null) { 16 throw new IllegalStateException( 17 "Cannot add header view to list -- setAdapter has already been called."); 18 } 19 20 FixedViewInfo info = new FixedViewInfo(); 21 info.view = v; 22 info.data = data; 23 info.isSelectable = isSelectable; 24 mHeaderViewInfos.add(info); 25 } 26 27 /** 28 * Sets the data behind this ListView. 29 * 30 * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter}, 31 * depending on the ListView features currently in use. For instance, adding 32 * headers and/or footers will cause the adapter to be wrapped. 33 * 34 * @param adapter The ListAdapter which is responsible for maintaining the 35 * data backing this list and for producing a view to represent an 36 * item in that data set. 37 * 38 * @see #getAdapter() 39 */ 40 @Override 41 public void setAdapter(ListAdapter adapter) { 42 if (null != mAdapter) { 43 mAdapter.unregisterDataSetObserver(mDataSetObserver); 44 } 45 46 resetList(); 47 mRecycler.clear(); 48 49 if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) { 50 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter); 51 } else { 52 mAdapter = adapter; 53 } 54 55 //其它的一些代码这里省略之... 56 }
从代码和注释里都可以很清楚的得知,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改成下面这样,就可以了
1 @Override 2 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 3 doSomething(parent.getAdapter().getItem(position)); 4 }
本文由Roy最初发表于:http://blog.chengbo.net/2012/03/09/onitemclick-return-wrong-position-when-listview-has-headerview.html,你可以在保持文章完整和保留本声明的情况下转帖、分发和印刷等。谢谢作者。