今天在做一個功能:在初始化ListView時,把第一行背景置為黃色,同時保存第一行對象,用於在點擊其他行時將該行重新置為白色。
if(position==0){
convertView.setBackgroundColor(Color.YELLOW);
lastconvertView=convertView;
}
結果運行時發現第一行的顏色一直會是黃色而無法改變。調試了之後發現getView中 if(position==0) 居然會多次進入,最終導致的結果便是我最後一次取得的lastconvertView並非listview上面的第一行。網上查了之後發現原因是因為未固定listview的高度導致的,但是root cause卻找不到說明。於是去翻閱了源碼+大量調試,大概推算出了原因,在此記錄。
首先是說明下ListView的顯示機制,listview的機制是這樣子的:
假如你有1000條數據,但是屏幕只能顯示10條,那麼當你第一次加載顯示的時候,會先創建10個View,1-10,當你拖動Listview,使1隱藏而11顯示的時候,系統會自動把填充1的View傳遞過來,注意看代碼Adapter的getView方法
@Override
public View getView(final int position, View convertView, ViewGroup parent)
這裡的converView就是1的view,一般的做法會把這個view拿來復用,作為11的view。
當我們固定listview的高度時(fill_parent或直接固定高度),那麼listview很容易就能計算出容器內可以顯示多少行。但如果我們使用了“wrap_content”,只有在屏幕內控件完全加載後才知道到底能顯示多少行數據時,ListView自身便會做一些嘗試性計算。在源碼中可以發現一些叫做onMeasure的方法,目測是做此用處(源碼略顯復雜,沒讀透)。
當listview計算出屏幕一共需要多少行後,如果listview自身高度不變,那麼它的容納的行數就不會變,使用getChildCount()可以得到它的最大行數。
再回到原來的問題,為什麼最後一次取得的結果不是listview的第一行呢? 將listview設置為“wrap_content”後用下面的測試代碼,看下輸出。
//獲取當前listview的個數 相等輸出個數和站點名 不相等輸出個數和"無"
if(listView.getChildCount() == position)
{
//child個數 當前position位置 +站名
Log.i("", listView.getChildCount()+" "+position+" "+coordInfo.stationname);
}
else {
//child個數 當前position位置+無
Log.i("", listView.getChildCount()+" "+position+" "+"無");
}
在我的測試應用中,listview剛好可以放11個view,看下輸出發現,listview在開始時,實例化了11個view進行填充,即前面10個“無”結尾的+第一個”客運中心“結尾的view,由此測量出了listview的容量。換句話說,這11個view都只是用於測量的臨時view。另外在正是填充完之後,listview再次創建了11個臨時view用於確認高度是否正確。而由於我的代碼邏輯設計失誤,在
進行到這一步時,由於position會再次等於0,因此會把一個臨時的view賦值給lastconvertView。
到此原因找到,同時將listview的高度設為fill_parent後,問題解決。
另外說下網上說的另外一個解決方法
if(parent.getchildcount() == position)
{
正常情況下應該執行的代碼
}
else
{
這裡就是多次加載的問題,可以不用理這裡面的 代碼,
}
這個方法是不可行的,因為在不改變listview高度的情況下,listview的getchildcount()在加載完成後是固定的,position指的卻是在adapter中的位置,當adapter的個數大於listview的容納個數時,該判斷條件不會成立,即滑動listview時,不會成立。