ListView setAdapter has already been called 异常

一  遇到问题
ListView HeaderView使用过程中遇到的问题
1. 必须在setAdapter之前执行addHeaderView方法,否则会出现以下异常
java.lang.IllegalStateException: Cannot add header view to list -- setAdapter has already been called.




二 分析源码查找原因
先来看问题1,查看下ListView.addHeaderView()源码分析原因
[java] view plain copy
 print?
  1. public void addHeaderView(View v, Object data, boolean isSelectable) {  
  2.   
  3.     // 只要mAdapter不为空就抛出此异常  
  4.     if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) {  
  5.         throw new IllegalStateException(  
  6.                 "Cannot add header view to list -- setAdapter has already been called.");  
  7.     }  
  8.   
  9.     ......  
  10. }  


再分析下问题2,查看ListView.setAdapter()源码分析原因
[java] view plain copy
 print?
  1. public void setAdapter(ListAdapter adapter) {  
  2.   
  3.     .....  
  4.   
  5.     if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {  
  6.         mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);  
  7.     } else {  
  8.         mAdapter = adapter;  
  9.     }  
  10.       
  11.     .....  
  12. }  



原来使用了新的adapter



三 为什么HeaderView需要包装一个HeaderViewListAdapter?
     为什么addHeaderView时adapter必须为空
1. 先来看下ListView的继承体系
ListView -> AbsListView -> AdapterView -> ViewGroup

2. ListView继承自ViewGroup,如果需要添加视图只能通过ViewGroup.addView()添加,而在AdapterView.addView中可以看到此方法已经不让使用
[java] view plain copy
 print?
  1. public void addView(View child) {  
  2.     throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");  
  3. }  


3. 只能通过adapter的getview处理添加view,ListView内部是如何添加View?以下是方法调用堆栈
[java] view plain copy
 print?
  1. AbsListView.onTouchEvent ->   
  2. AbsListView.scrollIfNeeded ->   
  3. AbsListView.trackMotionScroll  ->   
  4. AbsListView.fillGap ->   
  5. ListView.fillDown/fillUp/fillFromMiddle ->   
  6. ListView.makeAndAddView ->   
  7. ListView.setupChild ->   
  8. ListView.addViewInLayout  



也可以在第一个item的布局中添加,但是这种方式会对ListView造成影响

android listview addHeaderView和addFooterView的注意事项  :

1、item 内如果有button等控件时,在监听listview的onitemclick事件时,焦点会被item内的button、imagebutton等控 件抢走,
从而导致在listview设置了onitemclick事件后不会被触发。解决方法是在初始化item的时候屏蔽掉其内部button等控件的 焦点获取,
具体方法可以在自定义item的根控件中调用:

 setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 
这样就能阻塞字控件抢夺焦点,listview的onitemclick就能被正确触发,同时对item内部的button等控件也没有影响,他们在被点击时照样可以触发自身的点击事件。
 
2、当listview需要添加headerview时,可以通过调 用listview的addHeaderView(headView, null, false) 方法,
该方法还有一个重载方法 addHeaderView(headView);这两个方法的区别是前一个方法可以控制header是否可以被selected,如果不想被 selected则将第三个参数设置成false 
  
3、接着上面说的添加header,添加header时调用的 addHeaderView方法必须放在listview.setadapter前面,
意思很明确就是如果想给listview添加头部则必须在给其绑定 adapter前添加,否则会报错。
原因是当我们在调用setAdapter方法时会android会判断当前listview是否已经添加 header,
如果已经添加则会生成一个新的HeaderViewListAdapter,这个新的HeaderViewListAdapter包含我们 设置的adapter所有内容以及listview的header和footer。
所以当我们在给listview添加了header后在程序中调用 listview.getadapter时返回的是tempadapter而不是我们通过setadapter传进去的adapter。
如果没有设置 adapter则HeaderViewListAdapter与我们自己的adapter是一样的。 listview.getadapter().getcount()方法返回值会比我们预期的要大,原因是添加了header。

4、接着上面的HeaderViewListAdapter说,我们 自定义adapter里面的getitem方法里面返回的position是不包括header的,是我们自定义adapter中数据position编 号从0开始,也就是说与我们传进去的list的位置是一样的。

 

@Override

public View getView(int position, View convertView, ViewGroup parent) {

// TODO Auto-generated method stub

Log.i("adapter", "position:"+position); //这个position就是我们数据的真实位置

}

而listview的onitemclick方法中:

public void onItemSelected(AdapterView<?> parent, View view, int position, long id)

position是当前click的位置,这个位置是指在HeaderViewListAdapter中的位置,从0开始如果listview中添加了header则0代表header。4、接着上面的HeaderViewListAdapter说,我们 自定义adapter里面的getitem方法里面返回的position是不包括header的,是我们自定义adapter中数据position编 号从0开始,也就是说与我们传进去的list的位置是一样的。


public View getView(int position, View convertView, ViewGroup parent) {

// TODO Auto-generated method stub

Log.i("adapter", "position:"+position); //这个position就是我们数据的真实位置

}

而listview的onitemclick方法中:

public void onItemSelected(AdapterView<?> parent, View view, int position, long id)

position是当前click的位置,这个位置是指在HeaderViewListAdapter中的位置,从0开始如果listview中添加了header则0代表header。

 

 关于FooterView的添加和删除
 *   3.1每次总是先remove掉FooterView
 *   3.2若有需求再add上FooterView

 
 
 我们通常在加载数据时,为了省流量不会一次性把数据全部下完,一般是分段下载。
 分段下载一般会在listview最后面放一个进度条表示正在加载数据,当数据加载完时,我们又要清除它。这时候就要注意了。
mLoadingLayout = (FrameLayout) View.inflate(this, R.layout.load, null);
   listView.addFooterView(mLoadingLayout);
   listView.requestFocus();

这是listview尾部添加一个进度条。
listView.removeFooterView(mLoadingLayout);
这是移除尾部的进度条。
有时候在移除时回报空指针,但listview不为null ,mLoadingLayout也不为null,但还是报空指针,原因是因为listview要分为三部分。
一是头部,二是中间部,三是尾部。在设置了头部或尾部时,必须要有中间部才能真正意义上的生效。没生效就去移除就会报空指针错误。
所以在 
listView.removeFooterView(mLoadingLayout);时

必须先调用 listView.setAdapter(adapter);(设置中间部)

adapter可以数据可以为0但不可为null


发布了51 篇原创文章 · 获赞 11 · 访问量 15万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章