之前講到了自定義Adapter傳遞給ListView時(shí),因?yàn)長istView的View回收,需要注意當(dāng)ListView列表項(xiàng)中包含有帶有狀態(tài)標(biāo)識(shí)控件的問題。詳情可見之前發(fā)的帖[url=自定義Adapter實(shí)現(xiàn)ListView帶多選框等狀態(tài)控件的注意事項(xiàng) //www.companysz.com/article/33425.htm
還是這個(gè)問題,講一個(gè)我遇到的因?yàn)閮尚写a位置相反引起的問題。
我的ListView中每行View包含一個(gè)ImageView、TextView、CheckBox。當(dāng)ListView中有一個(gè)或一個(gè)一行CheckBox被選中就讓ListView上面的Button顯示,否則就隱藏。因此,需要對(duì)每行View中的CheckBox設(shè)置監(jiān)聽。我使用CheckBox中的OnCheckedChangeListener監(jiān)聽器,當(dāng)CheckBox的狀態(tài)發(fā)生改變的時(shí)候就會(huì)觸發(fā)這個(gè)監(jiān)聽器。先看下我自定義給ListView的Adapter的getView方法中的一些關(guān)鍵代碼:
這是getView方法中使用到的內(nèi)部類:
static class ViewHolder {
public ImageView imageView;
public TextView textView;
public CheckBox checkBox;
}
這是getView方法中利用ListView回收機(jī)制循環(huán)利用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();
}
接下來是對(duì)其中checkbox設(shè)置顯示狀態(tài)和監(jiān)聽器的代碼:
viewHolder.checkBox
.setOnCheckedChangeListener(new SearchItemOnCheckedChangeListener(
position, state));
viewHolder.checkBox.setChecked(state[position]);
之前說過了,因?yàn)長istView的回收,需要使用一個(gè)數(shù)組或list來記錄每項(xiàng)數(shù)據(jù)中checkbox的狀態(tài)。這里,state是與ListView列表等長的boolean數(shù)組,用于記錄每個(gè)position(也就是每個(gè)列表項(xiàng)數(shù)據(jù)的id)標(biāo)識(shí)的數(shù)據(jù)上checkbox應(yīng)該顯示的狀態(tài),初始的狀態(tài)都是false。構(gòu)造checkbox監(jiān)聽器的時(shí)候需要傳遞當(dāng)前View的position,以及整個(gè)列表checkbox的狀態(tài)數(shù)組state。以下是checkBox狀態(tài)改變監(jiān)聽器的代碼:
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的整型,用于記錄被選中多選框的數(shù)量。searchButton是根據(jù)checkbox而決定顯示還是隱藏的按鈕。
以上整個(gè)邏輯功能的實(shí)現(xiàn)代碼。開頭說了,這是一個(gè)我因?yàn)長istView的回收機(jī)制和兩行代碼位置相反引起的問題。兩行代碼的位置相反將導(dǎo)致完全不同的結(jié)果,所指的就是設(shè)置checkbox監(jiān)聽器和狀態(tài)的兩行代碼,起初我的順序?yàn)椋?
viewHolder.checkBox.setChecked(state[position]);
viewHolder.checkBox.setOnCheckedChangeListener(new SearchItemOnCheckedChangeListener(position, state));
這樣的順序出現(xiàn)的問題是,當(dāng)我拉動(dòng)列表后,因?yàn)槔瓌?dòng)被隱藏的列表項(xiàng)狀態(tài)將被更改為false。這很不可思議,因?yàn)槲乙呀?jīng)分離了一個(gè)狀態(tài)數(shù)組來記錄每個(gè)checkbox的狀態(tài),想來想去只有一個(gè)可能,就是狀態(tài)數(shù)組中的值改變了,而改變狀態(tài)數(shù)組的值位置就在于OnCheckedChangeListener中。Debug了幾個(gè)小時(shí),才想通了問題就在于這兩行代碼為位置順序。
起因還是得講到ListView的回收機(jī)制。假如我的ListView最多只能顯示10個(gè)View,那么起初就會(huì)調(diào)用十次getView構(gòu)造十個(gè)全新的View(包括對(duì)其中的checkbox設(shè)置監(jiān)聽器)。當(dāng)我將列表往下拉出現(xiàn)第11個(gè)列表項(xiàng)的時(shí)候,頂部第一個(gè)列表項(xiàng)被隱藏,同樣會(huì)再調(diào)用一次getView,不過此時(shí)getView的參數(shù)將返回剛剛被隱藏的第一個(gè)列表項(xiàng)的View,并對(duì)這個(gè)View更改數(shù)據(jù)作為即將出現(xiàn)的第11個(gè)View。問題就出在這里,我把checkbox.setChecked()方法調(diào)用放在了設(shè)置監(jiān)聽器前面,此時(shí)因?yàn)楦牧薱heckbox的狀態(tài),勢必引起觸發(fā)狀態(tài)更改的監(jiān)聽器。注意!由于第11個(gè)View是用被隱藏的第1個(gè)View回收來的,雖然還沒有執(zhí)行下一行設(shè)置監(jiān)聽器的代碼,但實(shí)際上它已經(jīng)擁有了一個(gè)狀態(tài)監(jiān)聽器,這個(gè)監(jiān)聽器是這個(gè)View還是作為第一個(gè)View時(shí)設(shè)置。那個(gè)時(shí)候的監(jiān)聽器設(shè)置更改的第一項(xiàng)的數(shù)據(jù),而不是第11項(xiàng)數(shù)據(jù)。因此,理所當(dāng)然不能正確更改第11項(xiàng)數(shù)據(jù),反而更改了無辜的第1項(xiàng)數(shù)據(jù)。如果我把兩行代碼順序反過來,先更改監(jiān)聽器,再設(shè)置狀態(tài),引發(fā)的監(jiān)聽器自然也就是新的監(jiān)聽器,邏輯也就對(duì)了。