ListView最終優化方法,絕對流暢

jia635發表於2014-07-06
ListView終極優化方法,絕對流暢
     listview可以說是Android開發中最常見的UI控制元件了,listview能夠以列表的方式顯示大量同類的資料,這樣問題就產生了,既然是大量資料,就會使用到很多佈局,給佈局繫結資料,listview將佔用大量資源還可能會產生卡頓現象。
     listview現在最常用也擁有很好的效能的優化方式是在Adapter中使用靜態的ViewHolder,具體程式碼如下:
      Activity
     private TestAdapter mAdapter;

    private String[] mArrData;
    
private TextView mTV;

    @Override
    
protected void onCreate(Bundle savedInstanceState) {
        
super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mTV 
= (TextView) findViewById(R.id.tvShow);

        mArrData 
= new String[1000];
        
for (int i = 0; i < 1000; i++) {
            mArrData[i] 
= "Google IO Adapter" + i;
        }
        mAdapter 
= new TestAdapter(this, mArrData);
        ((ListView) findViewById(android.R.id.list)).setAdapter(mAdapter);
    }
         Adapter
      private int count = 0;
    private long sum = 0L;

        @Override
        
public View getView(int position, View convertView, ViewGroup parent) {
            
// 開始計時
            long startTime = System.nanoTime();

            ViewHolder holder;
            
if (convertView == null) {
                convertView 
= mInflater.inflate(R.layout.list_item_icon_text,
                        
null);
                holder 
= new ViewHolder();
                holder.icon1 
= (ImageView) convertView.findViewById(R.id.icon1);
                holder.text1 
= (TextView) convertView.findViewById(R.id.text1);
                holder.icon2 
= (ImageView) convertView.findViewById(R.id.icon2);
                holder.text2 
= (TextView) convertView.findViewById(R.id.text2);
                convertView.setTag(holder);
            }
            
else{
                holder 
= (ViewHolder)convertView.getTag();
            }
            holder.icon1.setImageResource(R.drawable.icon);
            holder.text1.setText(mData[position]);
            holder.icon2 .setImageResource(R.drawable.icon);
            holder.text2.setText(mData[position]);

            
// 停止計時
            long endTime = System.nanoTime();
            
// 計算耗時
            long val = (endTime - startTime) / 1000L;
            Log.e(
"Test""Position:" + position + ":" + val);
            
if (count < 100) {
                
if (val < 1000L) {
                    sum 
+= val;
                    count
++;
                }
            } 
else
                mTV.setText(String.valueOf(sum 
/ 100L));// 顯示統計結果
            return convertView;
        }
    }

    
static class ViewHolder {
        TextView text1;
        ImageView icon1;
        TextView text2;
        ImageView icon2;
    }

     在Adapter的程式碼中,在getView方法裡首先判斷convertView是否為空,若為空則載入相應佈局,若不為空則直接使用該佈局,這能夠很有效的使用Android為listview提供的快取機制:只載入一屏的佈局,之後滑動出來的item使用的是之前已經載入的佈局的快取;
     而使用靜態的ViewHoulder的目的則是節省了findViewById的時間。如果不使用ViewHolder,每次getView的時候都需要得到一次子佈局,而這也是很耗時並且耗資源的;如果使用了ViewHolder作為子佈局的快取,使用View的setTag方法將快取與每個item繫結,則也可以省去了findViewById的事件;而將ViewHolder設定為static的目的是指在初始化Adapter時初始化一次這個內部類,否則將會在每次建立Adapter時都要初始化一次,而這是沒有必要的。

          上述方法能夠解決大部分listview消耗資源以及卡頓的問題,但對於不同的需求的listview來說還會存在其他讓listview卡頓的原因,比如listview的item每次載入時都需要獲得圖片並設定到imageview中,item載入時需要進行大量的計算,item裡的TextView需要設定指定字型;這些耗時的操作都會讓listview滑動起來很卡,帶來不好的體驗;
          這幾天我一直在研究Android4.0+系統聯絡人的原始碼,因為我發現,系統聯絡人採用的也是listview佈局,每一個item都有圖片文字,而且使用了fastscroller模式,對聯絡人以首字母進行了分來,同時Adapter還實現了SectionIndexer介面,能夠實現這種.

但是卻一點都不卡,而我自己做的一個類似的介面,卻十分卡頓,很影響使用者體驗;於是我找來了Android體統聯絡人的原始碼,自己建立起來了一個可以執行的程式,然後去研究它是如何實現這麼流暢的listview的;
       經過研究我發現系統聯絡人的listview使用的是自定義的listview:PinnedHeaderListView 它的定義是這樣的:
/**
 * A ListView that maintains a header pinned at the top of the list. The
 * pinned header can be pushed up and dissolved as needed.
 */ 
它給listview加了一個header即顯示在聯絡人介面最上方貼著螢幕頂部的那部分,同時它繼承自AutoScrollListView 而這個AutoScrollListView繼承自listview,對listview進行了一些優化,讓listview在互動到指定的item時更流暢;
       看到這裡,於是我使用了這個AutoScrollListView ,但效果不是很明顯,listview還是很卡;在系統聯絡人使用的listview中沒有找到解決方法,我開始研究它的Adapter;研究Adapter發現,系統的Adapter進行了很多層的封裝,完全淡化了geiView方法;使用了很多AsyncTask來載入不同的資料,然後使用了CursorLoader來將載入好的資料新增到Adapter裡,同時還將圖片的載入與資料的載入進行了分離,程式碼邏輯十分複雜;但通過這個我得出了一個結論:不要將任何的耗時操作放在listview的getView方法裡。
         在系統聯絡人的這些Adapter裡我發現getView方法中沒有任何的耗時操作,在設定圖片時圖片已經得到,對列表按照字母進行的分類也已經分類好了,存放在一個內部類裡;在得出這個結論之前我嘗試過很多系統聯絡人中的程式碼,但都沒有得到明顯的效果,經過大量的測試我得出了這個結論,並且在測試中得到了驗證。
        在我的專案中,listview的每一個item都有一個圖片,和很多TextView,而且所有的TextView都要設定非系統的字型;Adapter使用的是ViewHolder優化,在getView中的程式碼已經很少了,但是還是卡;我的listview中的資料是一個物件的List,在物件裡只存放了item需要展示的圖片的資源ID,或者是圖片的路徑,需要通過一些操作才能獲得圖片,而這些操作其實是很耗時的;於是我將原來的物件進行了更改,將圖片物件直接存放在item對應的物件中,然後再Adapter初始化的時候將這些物件初始化,雖然listview展示所需的時間稍微長了一點,但是結果是listview滑動流暢了很多;接著我又將從assets中獲得字型TypeFace的操作放在了Adapter初始化的方法中,並且將字型通過靜態的變數都存起來,然後再getView中只需為TextView設定一下taptface即可,不需要在從asset中獲取字型所花費的時間;通過上面兩步操作之後,我的listview一點都不卡了,十分流暢。
        綜上,listview的優化其實就是去找getView中的耗時操作,然後提取出來,要麼使用非同步的方式為item的佈局設定資料,要是實在需要同步,就只能在Adapter初始化時將資料準備好,然後再getView中只需繫結一下就行。

相關文章