开发过程在使用ListView时发现了这个问题,在给ListView添加了HeaderView或则FooterView(下面全部以FooterView举例子,它们两者的原理是相同的)之后,再去点击FooterView时候会出现crash,IDE给出的错误报告是,在ListView的onItemClick中出现IndexOutOfBoundsException这个异常。
ps:这时候通常我们为该ListView定制了自定义的Adapter,并且复写了其中的getItem方法,一般如下所示:
@Override
public Object getItem(int position) {
return datas == null ? null : datas.get(position);
}
通过在网上搜索后看到有两种解决方案:
方法1、改变在onItemClick中获得Item的方式:通常我们是通过yourAdapter.getItem(position)来获得相应位置上的item,其中这个yourAdapter是你自己为ListView定制的Adapter;ok,然后需要改成使用parent.getAdapter().getItem(position)来获得当前点击的item,这样就可以解决上述出现的问题(亲测可用);
方法2、还是在onItemClick中进行,在其中加上if((position - 1) != mListItems.size()){….do something} 这个条件。具体见http://blog.csdn.net/lambert519/article/details/49233789
这个我木有去测试;
貌似网上很少有小伙伴讲为什么能解决。所以这里说一下方法1能解决这个问题的原因吧。从IDE报出的错误就可以看出,ListView的size和访问的position中出现的问题;还可以确定的是这个问题是由于对ListView使用了addFooterView()方法造成的,那么我们来看一下究竟里面做了些什么,ok源码:
public void addFooterView(View v, Object data, boolean isSelectable) {
final FixedViewInfo info = new FixedViewInfo();
info.view = v;
info.data = data;
info.isSelectable = isSelectable;
mFooterViewInfos.add(info);
mAreAllItemsSelectable &= isSelectable;
// Wrap the adapter if it wasn't already wrapped.
if (mAdapter != null) {
if (!(mAdapter instanceof HeaderViewListAdapter)) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}
// In the case of re-adding a footer view, or adding one later on,
// we need to notify the observer.
if (mDataSetObserver != null) {
mDataSetObserver.onChanged();
}
}
}
ok,重点看一下这句
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter)
也就是对当前的mAdapter进行实例化,那么这个HeaderViewListAdapter是什么呢,其源码200多行,这里就不全部列出来了,找出其中比较重要的:
ArrayList mHeaderViewInfos;
ArrayList mFooterViewInfos;
......
public int getHeadersCount() {
return mHeaderViewInfos.size();
}
public int getFootersCount() {
return mFooterViewInfos.size();
}
......
public int getCount() {
if (mAdapter != null) {
return getFootersCount() + getHeadersCount() + mAdapter.getCount();
} else {
return getFootersCount() + getHeadersCount();
}
}
.....
public Object getItem(int position) {
// Header (negative positions will throw an IndexOutOfBoundsException)
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return mHeaderViewInfos.get(position).data;
}
// Adapter
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getItem(adjPosition);
}
}
// Footer (off-limits positions will throw an IndexOutOfBoundsException)
return mFooterViewInfos.get(adjPosition - adapterCount).data;
}
我们可以看到,mAdapter这时候的getCount就变成了getFootersCount() + getHeadersCount() + mAdapter.getCount()了,也就是说在添加FooterView之后,会把它也计入到yourAdapter的getCount()数量中。然后我们再看一下getItem()方法中,其中有两个注释是说在这两种情况下会抛IndexOutOfBoundsException异常,而且在满足条件时会返回headview和FooterView的data;
也就是说我们在调用parent.getAdapter().getItem(position)时(也就是ListView中的mAdapter.getItem(position))实际上就是HeaderViewListAdapter对象的getItem方法的调用,注意这时候并没有调用我们自己定制的Adapter中的getItem方法,而是直接用ListView中的mAdapter获得的;
现在我们来看一下我们直接调用yourAdapter.getItem(position)(这种情况下是会报错的),这时就会去我们定制的Adapter中去找复写的相应的getItem方法中的实现,这时候会去执行相应的:
@Override
public Object getItem(int position) {
return datas == null ? null : datas.get(position);
}
这时候问题就来了,假如说我们去点击FooterView,这时候会触发onItemClick,然后通过yourAdapter.getItem(position)来获得当前的item,也就是调用了上面重写的那个方法,这时候datas就是我们加载到listview的每个item的数据,假设datas.size() = m,那么如果postion <= m-1都是合理的范围(对应有相应的item),但是我们在点击FooterView时候,这时候position = m,而datas.get(position)是不合法的(datas的size为m),所以说找不到对应的item了,这时候就会报IndexOutOfBoundsException异常。这样就会出现我们IDE报的异常。
ok,现在知道为什么会产生异常了,剩下的就是找到相应的解决方法了。
除了上述的两个方法以外,个人觉得还有另外一种方法:我们可以从自定义的Adapter入手,因为问题出在getItem中,所以只需要对该方法进行处理即可,把上面重写的getItem改为以下即可:
方法3
@Override
public Object getItem(int position) {
if (mDatas != null && mDatas.size() > position) {
return mDatas.get(position);
} else {
return null;
}
}
可以看到我们添加了mDatas.size() > position这个条件,就是为了保证只对mDatas大小范围内的position去获得对应的item,其他情况下都返回null;
实际上可以看到,不管是使用方法1的parent.getAdapter().getItem(position),还是使用方法3中的处理方案,都是保证在点击超过list大小的position时返回null处理。
ps:在给Listview添加HeaderView或则FooterView时一定要在ListView的setAdapter是之前,否则在有些手机上会展示不出来对应的view,具体原因网上有很多说明,这里就不说了。