欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android ListView的OnItemClickListener详解

程序员文章站 2024-03-06 13:46:14
我们在使用listview的时候,一般都会为listview添加一个响应事件android.widget.adapterview.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); 
// 响应代码 
}