<p ><span >我们在使用ListView的时候,一般都会为ListView添加一个响应事件android.widget.AdapterView.OnItemClickListener。本文主要在于对OnItemClickListener的<a ></a><strong >position</strong>和id参数做详细的解释,我相信有些人在这上面走了些弯路。</span></p><p ></p><p>先来看一下官方的文档</p><table><tbody ><tr class="firstRow"><th ><strong >position</strong></th><td >The<strong >position</strong>of the view in the adapter.</td></tr><tr ><th >id</th><td >The row id of the item that was clicked.</td></tr></tbody></table><p>而这两行字并没有解释清楚position和id的区别。另外,我们还有个Adapter的getView方法。</p><p>public abstract View getView (int position, View convertView, ViewGroup parent)</p><p>这里也有一个position。</p><p></p><p>初步接触ListView的同学,一般会直接继承ArrayAdapter,然后(比如我),就想当然的认为OnItemClick的position和getView的position是一样的啊。于是我们就getItem(position)来获取相应的数据。</p><p></p><p>那么这段代码有没有错呢?如果有错的话,在什么情况会出错呢?</p><p>第一个问题的答案是,当我们为ListView添加headerView或者footerView之后,这段代码就不一定是我们想要的了。</p><p></p><p>出现问题的原因在于,当我们为ListView添加headerView或者footerView之后,ListView在setAdapter时,做了一些事情,这导致,Adapter和OnItemClickListener中的position含义发生了变化。</p><p></p><p></p><p>我们可以来看看ListView中setAdapter的实现</p><pre class="brush:php;toolbar:false">
publicvoidsetAdapter(ListAdapteradapter){
if(mAdapter!=null&&mDataSetObserver!=null){
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
resetList();
mRecycler.clear();
if(mHeaderViewInfos.size()>0||mFooterViewInfos.size()>0){
mAdapter=newHeaderViewListAdapter(mHeaderViewInfos,mFooterViewInfos,adapter);
}else{
mAdapter=adapter;
}</pre><p>可以看出,如果这个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)。</p><p></p><p>回到开头的问题,position和id有啥区别。为此,我们找一下position和id是怎么传进来的。</p><p>OnItemClickListener在android.widget.AdapterView的public boolean performItemClick(View view, int position, long id)函数中被调用。</p><p>performItemClick在android.widget.AbsListView.PerformClick.run() 中被调用</p><pre class="brush:php;toolbar:false">privateclassPerformClickextendsWindowRunnnableimplementsRunnable{
intmClickMotionPosition;
publicvoidrun(){
//Thedatahaschangedsincewepostedthisactionintheeventqueue,
//bailoutbeforebadthingshappen
if(mDataChanged)return;
finalListAdapteradapter=mAdapter;
finalintmotionPosition=mClickMotionPosition;
if(adapter!=null&&mItemCount>0&&
motionPosition!=INVALID_POSITION&&
motionPosition<adapter.getCount()&&sameWindow()){
finalViewview=getChildAt(motionPosition-mFirstPosition);
//Ifthereisnoview,somethingbadhappened(theviewscrolledoffthe
//screen,etc.)andweshouldcanceltheclick
if(view!=null){
performItemClick(view,motionPosition,adapter.getItemId(motionPosition));
}
}
}
}</pre><p>可以看到,position事实上就是ListView中被点击的view的位置。注意,在ListView中是不负责处理headerView和footViewer的,所以,这个位置应该是这个被点击的view在数组[所有的headerView,用户添加的view,所有的footerView]中的位置(请自行参考HeaderViewListAdapter的getView实现)。而id是来自于adapter.getItemId(position)。</p><p></p><p>对于ArrayAdapter的getItemId函数,实现就是return position。id和position是一致的。</p><p>然而,对于HeaderViewListAdapter</p><pre class="brush:php;toolbar:false">
publiclonggetItemId(intposition){
intnumHeaders=getHeadersCount();
if(mAdapter!=null&&position>=numHeaders){
intadjPosition=position-numHeaders;
intadapterCount=mAdapter.getCount();
if(adjPosition<adapterCount){
returnmAdapter.getItemId(adjPosition);
}
}
return-1;
}</pre><p>实现逻辑是,如果position指向了headerView或footerView,那么返回-1,否则,将返回在用户view数组的位置。</p><p>也就是说</p><p>id=position-headerView的个数(id < headerviewer的个数+用户view的个数),否则=-1</p><p>因此,OnItemClickListener的正确实现如下:</p><p></p><pre class="brush:php;toolbar:false">voidonItemClick(AdapterViewparent,Viewview,intposition,longid){
if(id==-1){
//点击的是headerView或者footerView
return;
}
intrealPosition=(int)id;
Titem=getItem(realPosition);
//响应代码
}</pre><p></p>