Android ListView的OnItemClickListener详解
我们在使用listview的时候,一般都会为listview添加一个响应事件android.widget.adapterview.onitemclicklistener。本文主要在于对onitemclicklistener的position和id参数做详细的解释,我相信有些人在这上面走了些弯路。
先来看一下官方的文档
position the position of the view in the adapter.
id the row id of the item that was clicked.
而这两行字并没有解释清楚position和id的区别。另外,我们还有个adapter的getview方法。
public abstract view getview (int position, view convertview, viewgroup parent)
这里也有一个position。
初步接触listview的同学,一般会直接继承arrayadapter,然后(比如我),就想当然的认为onitemclick的position和getview的position是一样的啊。于是我们就getitem(position)来获取相应的数据。
那么这段代码有没有错呢?如果有错的话,在什么情况会出错呢?
第一个问题的答案是,当我们为listview添加headerview或者footerview之后,这段代码就不一定是我们想要的了。
出现问题的原因在于,当我们为listview添加headerview或者footerview之后,listview在setadapter时,做了一些事情,这导致,adapter和onitemclicklistener中的position含义发生了变化。
我们可以来看看listview中setadapter的实现
public void setadapter(listadapter adapter) { if (madapter != null && mdatasetobserver != null) { madapter.unregisterdatasetobserver(mdatasetobserver); } resetlist(); mrecycler.clear(); if (mheaderviewinfos.size() > 0|| mfooterviewinfos.size() > 0) { madapter = new headerviewlistadapter(mheaderviewinfos, mfooterviewinfos, adapter); } else { madapter = adapter; }
可以看出,如果这个listview存在headerview或者footerview的话,那么会在我们传入的adapter外面在封装一层headerviewlistadapter,这是一个专门用来自动处理headerview和footerview的adapter。在listview中,本身不区分headerview,footerview。listview可以理解成是只负责管理一组view的数组的ui(viewgroup),headerview和footerview都委托给headerviewlistadapter来处理。(从这里也可以看到为什么api文档中提到,addfooterview和addheaderview要在setadapter函数之前调用,如果在之后调用,那么就不会生成headerviewlistadapter,从而导致显示不出headerview和footerview)。
回到开头的问题,position和id有啥区别。为此,我们找一下position和id是怎么传进来的。
onitemclicklistener在android.widget.adapterview的public boolean performitemclick(view view, int position, long id)函数中被调用。
performitemclick在android.widget.abslistview.performclick.run() 中被调用
private class performclick extends windowrunnnable implements runnable { int mclickmotionposition; public void run() { // the data has changed since we posted this action in the event queue, // bail out before bad things happen if (mdatachanged) return; final listadapter adapter = madapter; final int motionposition = mclickmotionposition; if (adapter != null && mitemcount > 0 && motionposition != invalid_<strong>position</strong> && motionposition < adapter.getcount() && samewindow()) { final view view = getchildat(motionposition - mfirstposition); // if there is no view, something bad happened (the view scrolled off the // screen, etc.) and we should cancel the click if (view != null) { performitemclick(view, motionposition, adapter.getitemid(motionposition)); } } } }
可以看到,position事实上就是listview中被点击的view的位置。注意,在listview中是不负责处理headerview和footviewer的,所以,这个位置应该是这个被点击的view在数组[所有的headerview,用户添加的view,所有的footerview]中的位置(请自行参考headerviewlistadapter的getview实现)。而id是来自于adapter.getitemid(position)。
对于arrayadapter的getitemid函数,实现就是return position。id和position是一致的。
然而,对于headerviewlistadapter
public long getitemid(int <strong>position</strong>) { int numheaders = getheaderscount(); if (madapter != null && <strong>position</strong> >= numheaders) { int adjposition = <strong>position</strong> - numheaders; int adaptercount = madapter.getcount(); if (adjposition < adaptercount) { return madapter.getitemid(adjposition); } } return -1; }
实现逻辑是,如果position指向了headerview或footerview,那么返回-1,否则,将返回在用户view数组的位置。
也就是说
id=position-headerview的个数(id < headerviewer的个数+用户view的个数),否则=-1
因此,onitemclicklistener的正确实现如下:
void onitemclick(adapterviewparent, view view, int <strong>position</strong>, long id){ if(id == -1) { // 点击的是headerview或者<strong>footerview</strong> return; } int realposition=(int)id; t item=getitem(realposition); // 响应代码 }