Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android ListView滑動過程中控件顯示重復/錯誤問題之原理分析及解決方案

Android ListView滑動過程中控件顯示重復/錯誤問題之原理分析及解決方案

編輯:關於Android編程

前言:

為了使ListView性能更優,最普遍的方法就是添加一個ViewHolder靜態類。

雖然性能有很大的提高,但是同樣也伴隨著Item控件內容顯示重復或錯亂的情況。

 

分析並解決如下兩個問題

一、控件數據未初始化而導致的顯示錯誤。

二、網絡異步加載導致出現顯示錯誤、重復。

 

如下我們來簡單分析一下ListView的緩存機制。我們整篇文章均以下圖的模型來舉例說明。

\

 

 

 

 

(圖片轉至http://www.cnblogs.com/xiaowenji/archive/2010/12/08/1900579.html)


 

上圖中的過程分析:

1.在getView()獲取每行的Item並向下滑動的過程中,如果Item1已經完全滑出屏幕,且緩存中沒有Item1對應的View,則將其put進緩存中。

2.將要滑入的Item8會先判斷緩存中是否有可用的Item,如果有則直接將緩存中對應的View拿過來復用。

3.ListView顯示剛剛滑入的Item8,並將ListView中的各個Item都執行刷新(getView())操作。

 

那麼問題來了...

一、控件數據未初始化而導致的顯示錯誤

執行Item8的getView()過程中由於復用的是Item1,那麼Item8的所有初始值就是Item1的值,如果在Item8中沒有對Item中任何控件重新賦值的話,那麼顯示的內容會和跟Item1一模一樣。

實例代碼:

    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder viewHolder;

        if (convertView == null){
            viewHolder = new ViewHolder();
            convertView = layoutInflater.inflate(R.layout.listview,parent,false);
            viewHolder.tvTitle = (TextView)convertView.findViewById(R.id.tv_title);
            convertView.setTag(viewHolder);
        }else{
            viewHolder = (ViewHolder)convertView.getTag();
        }
	//Item7以上的所有Item都不賦值
        if(position <= 7){
            viewHolder.tvTitle.setText(title + String.valueOf(position));
        }

        return convertView;
    }

運行效果如下

\

 

 

我們發現Item7之後由於未重新賦值,所以都是復用的ListView緩存機制中的View。

 

問題的解決:

這個問題比較好解決,就是把每項Item所有控件都賦值,這樣就把緩存中的初始數據給覆蓋掉。

代碼如下

    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder viewHolder;

        if (convertView == null){
            viewHolder = new ViewHolder();
            convertView = layoutInflater.inflate(R.layout.listview,parent,false);
            viewHolder.tvTitle = (TextView)convertView.findViewById(R.id.tv_title);
            convertView.setTag(viewHolder);
        }else{
            viewHolder = (ViewHolder)convertView.getTag();
        }

        //if(position <= 7){
            viewHolder.tvTitle.setText(title + String.valueOf(position));
        //}

        return convertView;
    }

 

效果圖:

 

\

 

 

 

問題解決。

 

二、網絡異步加載導致出現顯示錯誤、重復。

舉個例子,比如Item有點贊功能,由於比較懶,未做實例,所以來來來...大家跟我一起在腦海中想象吧。

我們假設用戶點了Item1的贊按鈕(此時開始請求服務器...),當請求服務器的過程中,你突然向下滑動。

如上圖所示,Item1被放入緩存,而Item8復用Item1的View。就在這時Item1的點贊請求剛剛請求成功,並執行顯示贊狀態的操作,此時Item8由於是復用的Item1的View,所以Item8的贊按鈕就會被莫名其妙的點亮。然後就蒙圈了,就開始尋思,誰?誰啊?你誰?圖片的加載錯誤也是這個原因。(圖片的解決方案http://www.trinea.cn/android/android-listview-display-error-image-when-scroll/)

 

3個解決方案:

1.不使用 ViewHolder就不存在這種問題,每次有Item滑入會重新創建控件,但這你能用嗎?好吧,願意用就用吧;

2.HashMap來標記對應的View(HashMap),如果item特別少你也可以用,如果Item多的話,有多少個View就會有多少個鍵值對,所以內存會越來越大, 也不科學;

3.Tag標記,最終方案。

下面我們來講解一下通過Tag標記來解決上述問題。

先貼一下純手打的代碼

   public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder viewHolder;

        if (convertView == null){
            viewHolder = new ViewHolder();
            convertView = layoutInflater.inflate(R.layout.listview,parent,false);
            viewHolder.btnZ = (Button)convertView.findViewById(R.id.button);
            convertView.setTag(viewHolder);
        }else{
            viewHolder = (ViewHolder)convertView.getTag();
        }
	viewHolder.btnZ.setTag(position);
        viewHolder.btnZ.setText(踩);

        viewHolder.btnZ.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(//訪問服務器成功){
                    if ((int) viewHolder.btnZ.getTag() == position) {
                        //異步顯示 viewHolder.btnZ.setText(贊); 的操作
                    }
                }
            }
        });
	return convertView;
    }

問題解決。

 

針對上面的代碼,講一下Tag在ListView中的所起的作用。

用戶在Item1點了贊按鈕後,此時Item1的Button進入請求服務器的過程中,用戶向下滑,Item1滑出,將View復用給Item8並讓其顯示之後,此時Item1和Item8同時控制著同一個View,只不過Item8在顯示的時候已經把復用的View重新按照它的數據覆蓋了一下並把View上viewHolder.btnZ的tag重新標記成它的標記,而假設此時恰好Item1的請求才剛剛成功並要刷新了一下自己的控件,然而根據上面的代碼...Item如果要顯示自己的加載成功之後的狀態,需要判斷一下tag和position,此時Item1執行viewHolder.btnZ.getTag()方法,發現tag已經是7了(Item8的),而Item1自己的position是0。所以,被無情的拒絕在了門外~

所以我們用這種對比的方法就可以很輕松的來避免Item顯示出錯的問題。

 

總結一下:

在出現異步加載的過程中,滑動ListView,Item被復用且前一個Item剛剛異步加載成功的情況下。

getTag()獲取的是當前正在顯示的”正確的Item標記,而position則很可能是復用View之前的那個Item。

如果了解上述原理,相信不管是圖片還是button神馬的都可以避免出現顯示錯誤的問題啦。

 

 
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved