Android 在ListView使用addHeader注意的问题

一些APP:
首页图片
他们布局很类似。顶部是搜索栏 往下依次是轮播,分类 ,活动主题,推荐产品列表,底部Bar。 如果用整个用ScrollView实现的话,实现起来很容易,但是最下面的推荐产品由于是不确定数量的,有可能还挺多,那么 1.产品列表用ListView,需要解决和ScrollView的冲突及ListView的高度问题 2.产品列表用ScrollView或着LinearLayout,数量多,图片多的话,由于没有缓存重用,有可能有性能问题

换做用ListView来实现,如果不用Header,用itemType来区分的话,要创建N个type,实现起来略显复杂。
ListView 对Header,Foot而的数量是没有限制的,用ListView和addHeader处理这种情形是比较合适的(当然顶部搜索和底部Bar是除外的)。

addHeader,addFooter这两个方法使用很简单,就不赘述。主要记录一下使用的时候可能会遇到的坑。

1.关于Header的隐藏和显示

ListView 对 Header相关的API共有 listView.getHeaderViewsCount(); listView.addHeaderView(View v); listView.removeHeaderView(View v); listView.areHeaderDividersEnabled(); 四个函数,没有控制顺序,及是否显示某个Header的API。 怎么隐藏一个Header? 如果不再展示可以使用emoveHeaderView,再次需要展示呢?就会有顺序的问题。 再有想到的是将headerView设为GONE,理论上这是对的,但是实际上却没有效果,留下了一个空白的占位,也就是说他的parent去正确的响应及布局。现在一般的解决方案是:如果要隐藏一个headerView,就将发的子View的可见性设为GONE或者高度设为0。显示的时候View的可见性设为VISIBLE. 最终的解决方案是: 开始的时候将所有的HeaderView都按照顺序使用addHeaderView添加到ListView中,通过控制headerView的子View的可见性来控制是否显示。

2.关于setAdapter和addHeaderView的执行顺序的问题

ListView想要添加headerview的话,就要通过addHeaderView这个方法,然后想要为ListView设置数据的话,就要调用setAdapter方法了。但是,在低版本的设备(API<=17)上会报 java.lang.IllegalStateException: Cannot add header view to list -- setAdapter has already been called.的异常。这是因为我们在addHeaderView之前已经调用了setAdapter. 通过源码来看: API-17(Android 4.2)

public void addHeaderView(View v, Object data, boolean isSelectable) {

        if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) {
            throw new IllegalStateException(
                    "Cannot add header view to list -- setAdapter has already been called.");
        }

        FixedViewInfo info = new FixedViewInfo();
        info.view = v;
        info.data = data;
        info.isSelectable = isSelectable;
        mHeaderViewInfos.add(info);

        // in the case of re-adding a header view, or adding one later on,
        // we need to notify the observer
        if (mAdapter != null && mDataSetObserver != null) {
            mDataSetObserver.onChanged();
        }
}

API-18(Android 4.3)

public void addHeaderView(View v, Object data, boolean isSelectable) {
        final FixedViewInfo info = new FixedViewInfo();
        info.view = v;
        info.data = data;
        info.isSelectable = isSelectable;
        mHeaderViewInfos.add(info);

        // 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 header view, or adding one later on,
            // we need to notify the observer.
            if (mDataSetObserver != null) {
                mDataSetObserver.onChanged();
            }
        }
}

可以看到在API<=17中,如果adapter不是空直接抛出异常,而在API>17中,对mAdapter 进行了判断,如果是空,先把header记录在mHeaderViewInfos,在setAdapter的时候再将header及adapter封装在HeaderViewListAdapter中,如果不是空直接构建一个HeaderViewListAdapter。 所以为了保证兼容性的问题,在setAdapter之前要把所有的header都加入到ListView里面。setAdapter之后不要再addHeaderView了。

3.在onItemClickListen中position问题

通过源码知道,如果一个ListView 存在Header或者Footer,ListView内部会将adapter封装在一个HeaderViewListAdapter里面,那么在onItemClickListen里面取到是包含header在内的item的位置,已经不是我们一般意义上的数据源的位置。所以实际上点击的position=取到的position-headerConunt;当然可以直接这样做换算,实际上HeaderViewListAdapter里面已经帮我们做了: HeaderViewListAdapter.java

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;
    }

在HeaderViewListAdapter的getItem()中adjPosition就是真正数据源中国的position。 所以在onItemClickListen中取数据的时候使用 parent.getAdapter().getItem(position);来获取你真正的实体。(自定义的Adapter要重写getItem()方法,不要返回null)