解決ListView滾動複用convertview和ViewHolder資料填充錯亂
我說下我的理解,最可能出現重複的情況就是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因為圖片為空就沒有賦值,而是直接跳過了。就類似下面這種。
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");
}
這樣問題就很好的解決了。
如果子控制元件為ImageView,當賦值內容為空時,直接設定預設的影象資源。如果使用setBackgroundResource可能出現預設的影象資源沒有真正的設定到ImageView,必須使用setImageResource。setBackgroundResource是View類中的方法,而setImageResource是ImageView自己的方法。使用前者可能導致影象沒有真正的設定到ImageView,此時ImageView顯示上一次賦值的影象。