一些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)