ListView添加header问题


 今天突然发现ListView的OnItemClickListener监听事件中的position返回是从1开始的,一直觉得很奇怪,在群里问了后,可能是headerView的问题,特意去查了一番,原来是如此:

特此记过!

(1)添加HeaderView之后尺寸布局被忽略。

通常添加头部的方法是 
?
1
2
3
LayoutInflater lif = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View headerView = lif.inflate(R.layout.header, null );
mListView.addHeaderView(headerView);

原因: 
lif.inflate(R.layout.header, null)丢失了XML布局中根View的LayoutParam,应该使用的是 
?
1
lif.inflate(R.layout.header, mListView, false );

(2)添加HeaderView之后导致OnItemClickListener的position移位

OnItemClickListener接口的方法: 
?
1
void onItemClick(AdapterView<?> parent, View view, int position, long id)

position通常是从0开始的,但是添加了HeaderView之后,position也会将HeaderView的数目计算进去。 
几个解决办法: 
1,手动计算真实的position位置: 
?
1
2
3
4
5
6
7
8
final headerCount = 1 ;
mListView.setOnItemClickListener( new OnItemClickListener() {
     @Override
     public void onItemClick(AdapterView<?> parent, View view,
             int position, long id) {
         Item item = myAdapter.getItem(position - headerCount);
     }
});

2,其实上面的步骤ListView已经为我们提供了,所以可以改写为: 
?
1
2
3
4
5
6
7
mListView.setOnItemClickListener( new OnItemClickListener() {
     @Override
     public void onItemClick(AdapterView<?> parent, View view,
             int position, long id) {
         Item item = parent.getAdapter().getItem(position);
     }
});
原因在源码中有比较清晰的解释: 
当有headerView被添加时,实际传递给ListView的adapter被包装,parent.getAdapter()返回真实被ListView使用的Adapter(HeaderViewListAdapter),HeaderViewListAdapter的getItem(int)方法处理了position的问题。 

 

 

Google了下,发现有个老外issue过一个bug,和我遇到的问题一样,不过这个bug被RomainGuy reject掉了,理由是,你用错了,请用getAdapter。这回答的太简洁了,完全没法理解,所以只好又去仔细研究ListView的代码,终于领会他的意思了。把其中addHeaderViewsetAdapter方法贴下来

/**
 * Add a fixed view to appear at the top of the list. If addHeaderView is
 * called more than once, the views will appear in the order they were
 * added. Views added using this call can take focus if they want.
 * <p>
 * NOTE: Call this before calling setAdapter. This is so ListView can wrap
 * the supplied cursor with one that that will also account for header
 * views.
 *
 * @param v The view to add.
 * @param data Data to associate with this view
 * @param isSelectable whether the item is selectable
 */
public void addHeaderView(View v, Object data, boolean isSelectable) {
    if (mAdapter != null) {
        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);
}

/**
 * Sets the data behind this ListView.
 *
 * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
 * depending on the ListView features currently in use. For instance, adding
 * headers and/or footers will cause the adapter to be wrapped.
 *
 * @param adapter The ListAdapter which is responsible for maintaining the
 *        data backing this list and for producing a view to represent an
 *        item in that data set.
 *
 * @see #getAdapter()
 */
@Override
public void setAdapter(ListAdapter adapter) {
    if (null != mAdapter) {
        mAdapter.unregisterDataSetObserver(mDataSetObserver);
    }

    resetList();
    mRecycler.clear();

    if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
        mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
    } else {
        mAdapter = adapter;
    }

    //其它的一些代码这里省略之...
}

从代码和注释里都可以很清楚的得知,addHeaderView一定要在setAdapter之前调用,如果不这样做,addHeaderView会抛出一个异常。Android为什么要这样?这是因为,在setAdapter的时候,会针对我遇到的这种情况(也就是添加Header后position不正确的这种情况)做些特殊的处理。setAdapter在内部判断了当前ListView是否有Header或者Footer,如果没有,就直接使用参数传进来的adapter;如果有,则用一个decorated的HeaderViewListAdapter来替换参数。这个HeaderViewListAdapter的使命,就是排除Header和Footer,让position(当然也包括getItem,getItemId)等方法的position参数)正确返回。

分析到这里,解决方案就出来了:在onItemClick不要直接使用我们声明的adapter,而是用ListView里的那个decorated adapter。获取它的方法就是调用parent.getAdapter()。当然,如果ListView没有Header和Footer,直接使用声明的adapter也没有问题,不过为了避免出错,还是统一使用decorated adapter比较好。

把onItemClick改成下面这样,就可以了

@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
    doSomething(parent.getAdapter().getItem(position));
}

本文由Roy最初发表于:http://blog.chengbo.net/2012/03/09/onitemclick-return-wrong-position-when-listview-has-headerview.html,你可以在保持文章完整和保留本声明的情况下转帖、分发和印刷等

(3)LayoutInflater的infalte()

用来呼应第一个问题。LayoutInflater的作用很简单,就是将XML的布局文件“翻译”成相应的View对象,而且出于性能的考虑,LayoutInflater只能处理编译后的XML文件,而不能处理通常明文编码的XML文件。 
最常用的一个方法: 
?
1
View inflate( int resource, ViewGroup root, boolean attachToRoot)

其中: 
resource 是布局文件ID 
root 是父ViewGroup对象, 
attachToRoot 是是否将“翻译”出来的View添加到上面的root中 

root和attachToRoot是共同作用的: 
1,有root,同时attachToRoot为false,那么inflate()返回的就是“翻译”得到的view 
2,有root,同时attachToRoot为true,那么inflate()就是将“翻译”得到的view添加到root后,然后返回root 
3,无root,同时attachToRoot为false,那么inflate()返回的就是“翻译”得到的view。 
4,无root,同时attachToRoot为true,报错。 

另外,root还有一个重要的作用就是为“翻译”得到的view添加合适的LayoutParam,并且如果并不想将得到的View添加到root的话,传递何种root是并没有要求的,比如: 
?
1
2
3
View view = mLayoutInflater.inflate(R.layout.header, new ListView(mContext), false );
View view = mLayoutInflater.inflate(R.layout.header, new LinearLayout(mContext), false );
View view = mLayoutInflater.inflate(R.layout.header, new RelativeLayout(mContext), false );
上面得到的View,除了view的LayoutParam分别为AbsListView.LayoutParams,LinearLayout.LayoutParams,RelativeLayout.LayoutParams之外,内容都一致。 


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值