之前講到了自訂Adapter傳遞給ListView時,因為ListView的View回收,需要注意當ListView清單項目中包含有帶有狀態標識控制項的問題。詳情可見之前發的帖[url=自訂Adapter實現ListView帶多選框等狀態控制項的注意事項 http://www.eoeandroid.com/thread-248749-1-1.html
還是這個問題,講一個我遇到的因為兩行代碼位置相反引起的問題。
我的ListView中每行View包含一個ImageView、TextView、CheckBox。當ListView中有一個或一個一行CheckBox被選中就讓ListView上面的Button顯示,否則就隱藏。因此,需要對每行View中的CheckBox設定監聽。我使用CheckBox中的OnCheckedChangeListener監聽器,當CheckBox的狀態發生改變的時候就會觸發這個監聽器。先看下我自訂給ListView的Adapter的getView方法中的一些關鍵代碼:
這是getView方法中使用到的內部類:
static class ViewHolder { public ImageView imageView; public TextView textView; public CheckBox checkBox; }
這是getView方法中利用ListView回收機制迴圈利用View的代碼:
public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { convertView = inflater.inflate(R.layout.searchitem, null); viewHolder = new ViewHolder(); viewHolder.imageView = (ImageView) convertView .findViewById(R.id.searchitemimage); viewHolder.textView = (TextView) convertView .findViewById(R.id.searchitemtext); viewHolder.checkBox = (CheckBox) convertView .findViewById(R.id.searchitemcheckbox); convertView.setTag(viewHolder); } else {// Log.i(CodeUtils.SEARCHTAG, "view is reuse"); viewHolder = (ViewHolder) convertView.getTag(); }
接下來是對其中checkbox設定顯示狀態和監聽器的代碼:
viewHolder.checkBox .setOnCheckedChangeListener(new SearchItemOnCheckedChangeListener( position, state)); viewHolder.checkBox.setChecked(state[position]);
之前說過了,因為ListView的回收,需要使用一個數組或list來記錄每項資料中checkbox的狀態。這裡,state是與ListView列表等長的boolean數組,用於記錄每個position(也就是每個清單項目資料的id)標識的資料上checkbox應該顯示的狀態,初始的狀態都是false。構造checkbox監聽器的時候需要傳遞當前View的position,以及整個列表checkbox的狀態數組state。以下是checkBox狀態改變監聽器的代碼:
public class SearchItemOnCheckedChangeListener implements OnCheckedChangeListener { private int id; private Boolean[] state; public SearchItemOnCheckedChangeListener(int id, Boolean[] state) { this.id = id; this.state = state; } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { state[id] = isChecked; if (isChecked) { checkedCount++; }else{checkedCount--;} if (checkCoutn>0) { searchButton.setVisibility(Button.INVISIBLE); } else { searchButton.setVisibility(Button.VISIBLE); } } } }
這裡面checkedCount初始值為0的整型,用於記錄被選中多選框的數量。searchButton是根據checkbox而決定顯示還是隱藏的按鈕。
以上整個邏輯功能的實現代碼。開頭說了,這是一個我因為ListView的回收機制和兩行代碼位置相反引起的問題。兩行代碼的位置相反將導致完全不同的結果,所指的就是設定checkbox監聽器和狀態的兩行代碼,起初我的順序為:
viewHolder.checkBox.setChecked(state[position]); viewHolder.checkBox.setOnCheckedChangeListener(new SearchItemOnCheckedChangeListener( position, state));
這樣的順序出現的問題是,當我拉動列表後,因為拉動被隱藏的清單項目狀態將被更改為false。這很不可思議,因為我已經分離了一個狀態數組來記錄每個checkbox的狀態,想來想去只有一個可能,就是狀態數組中的值改變了,而改變狀態數組的值位置就在於OnCheckedChangeListener中。Debug了幾個小時,才想通了問題就在於這兩行代碼為位置順序。
起因還是得講到ListView的回收機制。假如我的ListView最多隻能顯示10個View,那麼起初就會調用十次getView構造十個全新的View(包括對其中的checkbox設定監聽器)。當我將列表往下拉出現第11個清單項目的時候,頂部第一個清單項目被隱藏,同樣會再調用一次getView,不過此時getView的參數將返回剛剛被隱藏的第一個清單項目的View,並對這個View更改資料作為即將出現的第11個View。問題就出在這裡,我把checkbox.setChecked()方法調用放在了設定監聽器前面,此時因為更改了checkbox的狀態,勢必引起觸發狀態更改的監聽器。注意!由於第11個View是用被隱藏的第1個View回收來的,雖然還沒有執行下一行設定監聽器的代碼,但實際上它已經擁有了一個狀態監聽器,這個監聽器是這個View還是作為第一個View時設定。那個時候的監聽器設定更改的第一項的資料,而不是第11項資料。因此,理所當然不能正確更改第11項資料,反而更改了無辜的第1項資料。如果我把兩行代碼順序反過來,先更改監聽器,再設定狀態,引發的監聽器自然也就是新的監聽器,邏輯也就對了。
原文:http://www.eoeandroid.com/thread-249189-1-1.html