天天看點

ListView的性能優化之convertView和viewHolder

原文位址:http://blog.fangjie.info/2014/05/14/ListView的性能優化之convertView和viewHolder/#more

ListView優化大緻從以下幾個角度:

1.複用已經生成的convertView;

2.添加viewHolder類;

一、複用convertView

首先講下ListView的原理:ListView中的每一個Item顯示都需要Adapter調用一次getView的方法,這個方法會傳入一個convertView的參數,傳回的View就是這個Item顯示的View。如果當Item的數量足夠大,再為每一個Item都建立一個View對象,必将占用很多記憶體,建立View對象(mInflater.inflate(R.layout.lv_item, null);從xml中生成View,這是屬于IO操作)也是耗時操作,是以必将影響性能。Android提供了一個叫做Recycler(反複循環器)的構件,就是當ListView的Item從上方滾出螢幕視角之外,對應Item的View會被緩存到Recycler中,相應的會從下方生成一個Item,而此時調用的getView中的convertView參數就是滾出螢幕的Item的View,是以說如果能重用這個convertView,就會大大改善性能。

ListView的性能優化之convertView和viewHolder

圖解:一個螢幕最多顯示7個Item,如果當Item1滑出螢幕,此時Item1 的View被添加進Recycler中,相應的在下部要産生一個Item8,這時調用getView方法,convertView參數就是Item1 的View。

(1)Item固定高度

測試Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
      
public View getView(int position, View convertView, ViewGroup parent) {
     System.out.println("getView " + position + " " + convertView);
     ViewHolder holder = null;
     if (convertView == null) {
         convertView = mInflater.inflate(R.layout.lv_item, null);
         holder = new ViewHolder();
         holder.textView = (TextView)convertView.findViewById(R.id.tv_text);
         convertView.setTag(holder);
     } else {
         holder = (ViewHolder)convertView.getTag();
     }
     holder.textView.setText(mData.get(position));
     return convertView;
 }      

繼續向上滑動螢幕,當Item1,Item2,Item3…..陸續滑出螢幕之後,他們之前的View得到重用。是以最終螢幕上就隻是這10個View反複循環重用了(因為螢幕最多顯示10個Item)。

ListView的性能優化之convertView和viewHolder

然後我嘗試把螢幕往復原,重新滾到前面。

ListView的性能優化之convertView和viewHolder

圖解:螢幕從顯示Item 17-Item26 滾到顯示Item13-Item22。分析這個過程,當滾動一個Item時,Item26消失,Item16出現,是以Item16又将複用Item26的View,觀察Log,的确如此,其他依次類推。仔細分析,有這樣一個規律:無論怎麼重用convertView,一個position的Item永遠使用一個view。(比如此例中Item13 永遠顯示的是 5272fb00的View)。

(2)Item高度不固定

以上的例子是針對于每個Item的高度是固定的,是以很容易從定量中計算出每一個螢幕能夠顯示的最多Item數量。如果當一個Item的高度很根據不同情況的資料發生變化時,這樣就無法得知最多顯示的Item數量。

看一個例子,我的微部落格戶端程式,都知道微網誌有的有轉載,有的有圖檔,是以說微網誌的每一個Item高度是不固定的。同樣在getView中打出日志,顯示如下圖:

ListView的性能優化之convertView和viewHolder

圖解:因為沒有固定的Item高度,無法計算一個螢幕中能夠顯示的最大高度,系統會會先建立一個View,第一輪是用這個View來試探能放多少個item,試探出結果可以放3個Item,是以第二輪的0-2才是真正建立的View,螢幕上顯示了3個Item。當往下滾時,Item0沒有完全出去,下面有來了個Item3,是以這時的Item有建立了一個View,螢幕上此時顯示4個Item。之後4個Item就是做多顯示的數量,再往上滾動,convertView就開始重用了,Item4和Item0的View是一個對象。

二、使用viewHolder類

我們都知道在getView方法中的操作是這樣的:先從xml中建立view對象(inflate操作,我們采用了重用convertView方法優化),然後在這個view去findViewById,找到每一個子View,如:一個TextView等。這裡的findViewById操作是一個樹查找過程,也是一個耗時的操作,是以這裡也需要優化,就是使用viewHolder,把每一個子View都放在Holder中,當第一次建立convertView對象時,把這些子view找出來。然後用convertView的setTag将viewHolder設定到Tag中,以便系統第二次繪制ListView時從Tag中取出。當第二次重用convertView時,隻需從convertView中getTag取出來就可以。

測試Demo:

public View getView(int position, View convertView, ViewGroup parent) {
         System.out.println("getView " + position + " " + convertView);
         ViewHolder holder = null;
         if (convertView == null) {
             convertView = mInflater.inflate(R.layout.lv_item, null);
             holder = new ViewHolder();
             holder.textView = (TextView)convertView.findViewById(R.id.tv_text);
             convertView.setTag(holder);
         } else {
             holder = (ViewHolder)convertView.getTag();
         }
         holder.textView.setText(mData.get(position));
         return convertView;
     }
}

 public static class ViewHolder {
     public TextView textView;
 }      

繼續閱讀