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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章