在使用ListView時,很多情況下由于某些數據的加載不能立刻完成,為了防止UI線程的阻塞我們通常會采用異步加載數據的方式。這時就會產生一個問題:數據加載錯位!
原因分析
造成ListView的數據加載錯位是由于我們同時使用convertView和異步加載引起的。
- 圖一:假設我們屏幕上一開始顯示了5個完整的Item,針對這5個Item convertView都為null。
- 圖二:當第1個Item有一半移出屏幕,第6個Item有一半移入屏幕時,屏幕上顯示的Item條目數最多。也即我們擁有6個不同的convertView。
- 圖三:當第1個Item完全滑出屏幕之后,如果第7個Item進入屏幕,則此時會復用與第1個Item相關聯的convertView。
Paste_Image.png
假設我們的第一個Item的數據加載需要較長的時間,我們采用了異步加載的方式。想像一下當我們已經啟動了異步加載線程,但是在數據還沒有完全加載完成的時候,我們的ListView的狀態已經由圖一變成了圖三。此時如果加載完畢,由于數據加載線程并不知道ListView已經發生了改變,依舊會將數據更新至與Item1相關聯的convertView上。此時Item7就會顯示Item1的數據!!這就是所謂的ListView加載錯位。
問題解決
追根究底加載錯位的問題出在加載線程在數據加載完畢之后不知道ListView已經發生了改變,從這一點考慮我們可以通過以下步驟進行解決:
- 在啟動加載線程之前給每個Item的控件設定與當前Item關聯的Tag.
- 在數據加載完畢之后通過從控件中getTag()的方法判斷是否為“正確”的關聯控件。
核心代碼實現:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v;
final ViewHolder viewHolder;
final TypeInfo typeInfo = getItem(position);
Log.d("MyAdapter", "convertView:"+convertView+" position="+position);
if(convertView == null){
v= mInflater.inflate(mLayoutId, parent, false);
viewHolder = new ViewHolder();
viewHolder.tv_title = (TextView)v.findViewById(R.id.title);
viewHolder.iv_pic = (ImageView)v.findViewById(R.id.pic);
v.setTag(viewHolder);
}
else{
v = convertView;
viewHolder = (ViewHolder)v.getTag();
}
viewHolder.tv_title.setText(typeInfo.typeTitle);
// ------1.不添加以下代碼則會造成圖片錯位
viewHolder.iv_pic.setTag(typeInfo.typeTitle);
// ------
if(position == 0){
viewHolder.iv_pic.setImageDrawable(new BitmapDrawable());
// 模擬網絡、文件加載圖片的時間,這里假設加載用了3s
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
//注意這里的typeInfo.typeTitle是在啟動線程的時候就已經獲取到值了,而不是run到此處時才獲取到的值
Log.d("MyAdapter", "tag:"+typeInfo.typeTitle);
//運行到此處時模擬加載已經完成
// ------2.不添加以下代碼則會造成圖片錯位
Log.d("MyAdapter","tag:"+viewHolder.iv_pic.getTag());
if(viewHolder.iv_pic.getTag() == null)
return;
if(!viewHolder.iv_pic.getTag().equals(typeInfo.typeTitle))//不是真正的pos0的imageView控件
return;//do nothing
// ------
viewHolder.iv_pic.setImageResource(typeInfo.typePic);
}
}, 3000);
}else{
viewHolder.iv_pic.setImageResource(typeInfo.typePic);
}
return v;
}