本文轉自: http://www.cnblogs.com/3dant/archive/2011/04/06/2007060.html
我說下我的理解,最可能出現重復的情況就是getView(int position, View convertView, ViewGroup parent)中的convertview利用的情況,由於getview的時候,listview自身會復用已存在的item,即重用最先新建的那幾個item,還有就是注意tag的使用,convertView.getTag()返回的也是重用的view,其狀態是和被重用的一樣,包括圖片的顯示與隱藏的狀態,進度條的刷新等,都會被重用,這就出現了圖片或者進度條錯亂的情況,這時就需要在getview的時候,先把狀態復原(即隱藏進度條,去掉沒有下載的item的監聽,圖片設為默認等等),然后再根據判斷條件修改各個item的狀態。
這些天用到了ListView,由於要用到ImageView,且圖片源不是在資源里面的,也就沒法用到ID了,也就不能用SimpleAdapter之類的了。只能自己改寫一個Adapter,直接繼承BaseAdapter。由於一開始只是在網上看了一下如何寫getView這個方法,根本沒有去進一步理解各個參數的含義(當然現在也沒有全理解。。。),一樣一來,運行沒問題了,ListView里面數據也有了,結果來了個Bug,滾動的時候有些地方會重復前面出現過的圖片(或者說是第一頁出現去的圖片)。
正常情況下的getView方法體
public View getView(int position, View convertView, ViewGroup parent){
}
里面比較糾結的就是View convertView。
converView就是ListView里面每條記錄(Item)的樣式布局。
在ListView里面每顯示一條記錄就會記錄就會調用一次getView。但是為了優化速度,它只會緩存當前屏幕所顯示的記錄的View。這個可以在getView里面加一個輸出語句,看getView什么時候執行,執行過多少次。就明白了。
private
LayoutInflater mInflater;
@Override
public
View getView(
int
position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder myViews;
if
(convertView ==
null
){
System.out.println(
"為空:"
+ position);
myViews =
new
ViewHolder ();
convertView = mInflater.inflate(R.layout.lst_item,
null
);
myViews.mNameText = (TextView) convertView.findViewById(R.id.clst);
myViews.mPhoto = (ImageView) convertView.findViewById(R.id.mphoto);
convertView.setTag(myViews);
}
else
{
myViews = (ViewHolder ) convertView.getTag();
System.out.println(
"不為空:"
+ position);
}
Info p = infoList.get(position);
String dn = p.getDisplayName;
If (!dn==
null
){
myViews.mNameText.setText(dn);
}
Bitmap bm = p.getPhoto();
If(!p ==
null
){
myViews.mPhoto.setImageBitmap(bm);
}
//myViews.mNameText.setText(dn);
//Bitmap bm = p.getPhoto();
//myViews.mPhoto.setImageBitmap(bm);
return
convertView;
}
static
class
ViewHolder {
private
TextView mNameText;
private
ImageView mPhoto;
}
|
回到問題上來:
出現重復內容,基本上都是使用了ViewHolder這種方法的。
當我們判斷 convertView == null 的時候,如果為空,就會根據設計好的List的Item布局(XML),來為convertView賦值,並生成一個viewHolder來綁定converView里面的各個View控件(XML布局里面的那些控件)。再用convertView的setTag將viewHolder設置進去。
如果convertView不為空的時候,就會直接用convertView的getTag(),來獲得一個ViewHolder。
后面就是對ViewHolder里面那些控件來進行設置,比如顯示文字,顯示圖片什么的了。
如果再接下來的設置中,有某些條記錄的某些控件沒有被賦值,比如TextView因為要設置的內容為空,或者ImageView因為圖片為空就沒有賦值,而是直接跳過了。就類似下面這種。
1
2
3
4
5
6
7
8
|
String t = XXX.getName;
Bitmap p = XXX.getPhoto;
If (!t ==
null
){
mViewHolder.nameText.setText(t);
}
If(!p ==
null
){
mViewHolder.photoView.setImageBitmap(p);
}
|
前面說過,ListView只會緩存第一屏里面的List Item的視圖布局,之后滾動ListView后面的Item的布局就都是用前面所緩存的視圖布局(也就是convertView不為null)。這樣如果當后面的某一條記錄里面的某些控件因上面的原因沒有賦值,就會直接使用前面所緩存的視圖里面的值了(里面有值的話)。
這樣的最終效果就是,滾動的時候,會出現前面已經出現過的內容。
最簡單的解決方法就是,在上面的代碼中不去判斷賦值內容是否為空,而是直接設定對應該控件的值,即使用事例代碼中的注釋部分。(去掉上面代碼中的if段)
真正的解決方法,則規避不對ViewHolder中的元素進行賦值這種情況。拿上面的代碼來說:
If (!t == null){ mViewHolder.nameText.setText(t); }
這個時候,在t == null 時,就沒有對viewHolder進行賦值,所以在t == null時,界面上的元素就有可能是沒有更新的,也就是重復上一個(這個位置視圖)。所以可以加上一個else,並在里面對viewholder進行賦值。
If (!t == null){ mViewHolder.nameText.setText(t); }else{ mViewHolder.nameText.setText("unknow"); }
這樣問題就很好的解決了。