Listview添加完HeaderView或者FooterView后,点击相应的view出现IndexOutOfBoundsException问题修复

问题描述

开发过程在使用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,具体原因网上有很多说明,这里就不说了。

你可能感兴趣的:(android)