ViewHolder 復用機制
在使用 ListView 過程中適配器 Adapter 中的 getView() 方法中已經(jīng)通過 convertView 復用機制(RecycleBin 回收再利用) 進行了優(yōu)化烤送。
但我們發(fā)現(xiàn)代碼中仍然存在可以改進的地方,觀察如下代碼
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, null);
} else {
view = convertView;
}
ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return view;
}
我們發(fā)現(xiàn)雖然 convertView 是實現(xiàn)了復用,但是每次 getView() 時鹅搪,都要對每個子 View 中包含的控件進行實例化(findViewById)操作,這也是一個耗時的操作翁巍,這里就使用到了 ViewHolder 這個類來進行優(yōu)化操作子姜。
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;
}
如上代碼所述,我們把每個子 View 中包含的控件(需要實例化的)都放到 ViewHolder 的靜態(tài)內(nèi)部類中弓叛。當?shù)谝淮蝿?chuàng)建 convertView 對象時,把 ViewHolder 中的控件都進行實例化诚纸,然后用 convertView 的 setTag() 方法將 ViewHolder 設(shè)置到 Tag 中撰筷,以便系統(tǒng)第二次調(diào)用 getView() 方法時子 View 中需要的控件可以直接通過 convertView.getTag() 方法取出 ViewHolder 容器對象,然后直接調(diào)用需要的控件即可畦徘。
ListView 中的 convertView 和 ViewHolder 協(xié)同
ListView 里面的每一個 item 都是通過 adapter 來得到的毕籽,然后根據(jù) listView 的高度和 item 的高度來循環(huán)加載顯示在 listView 上抬闯,因此首次加載就決定了能夠顯示出來的 item 的個數(shù),并且首次走進的都是 getView() 方法里 convertView==null 的時候关筒,也就是說溶握,此時加載了幾個 item(包括只顯示一部分的),就創(chuàng)建了幾個 ViewHolder 對象蒸播,ViewHolder 里的控件來指向?qū)?yīng)的 convertView 里的控件睡榆。
圖中顯示了 ListView 顯示出的6個 item,和對應(yīng)生成的6個 ViewHolder 對象廉赔。并且在 ListView 工作過程中一直指向它們各自的 item肉微。注意此時,RecycleBin 里還么有任何可以拿來復用的 convertView蜡塌。若此時碉纳,ListView 向上滑動一部分,使得 item 1 一部分滾出 ListView 的 頂部馏艾,并且 item 7 也進入一部分劳曹,因為此時 RecycleBin 里沒有任何可以復用的 convertView(廢棄緩存里沒有 View),所以琅摩,程序急促進入 convertView == null铁孵,ViewHolder 7也被創(chuàng)建,指向 item 7房资。如果此時繼續(xù)滑動 item 1蜕劝,使得頁面剛好顯示的是 item2-7, 將不會再生成新的 ViewHolder轰异。此時滾出去的 item 1對應(yīng)的 View 將被放入 RecycleBin 里面做備用的 convertView 1岖沛,如果繼續(xù)往上滑動,item 8即將進入 ListView搭独,此時 ListView 的 adapter 在 RecycleBin 里找到了可以復用的 convertView 1婴削,而且因為之前用了 setTag 的方式,因此可以用 getTag 迅速獲得 ViewHolder 1牙肝,并且 ViewHolder 1是始終指向 convertView 1唉俗,因此直接可以根據(jù) position 來設(shè)置對應(yīng)的圖片,文本然后 return 此更新后的 View 給 ListView 來顯示 item 8配椭。
做 ViewHolder 來優(yōu)化 ListView 時虫溜,需要用 static 來修飾 ViewHolder 類?
我們把 ViewHolder 類設(shè)置成 static 靜態(tài)類颂郎,是想讓整個內(nèi)存中之需要一份 ViewHolder 對象吼渡,來優(yōu)化 ListView。
但是通過實驗發(fā)現(xiàn)
//把ViewHolder變?yōu)閟tatic之后乓序,getView()里加上如下代碼:
viewHolder = new ViewHolder();
AppLog.i("viewHolder" + i +" = " + viewHolder );
i++;
20.548 12491-12491/? [getView()] - viewHolder1 = $ViewHolder@e6a668d
20.627 12491-12491/? [getView()] - viewHolder2 = $ViewHolder@22e2da45
20.629 12491-12491/? [getView()] - viewHolder3 = $ViewHolder@355bd5c1
20.632 12491-12491/? [getView()] - viewHolder4 = $ViewHolder@210f0cfd
20.635 12491-12491/? [getView()] - viewHolder5 = $ViewHolder@3c47bf9
20.637 12491-12491/? [getView()] - viewHolder6 = $ViewHolder@35c7deb5
20.640 12491-12491/? [getView()] - viewHolder7 = $ViewHolder@e64b131
20.642 12491-12491/? [getView()] - viewHolder8 = $ViewHolder@34222f6d
20.644 12491-12491/? [getView()] - viewHolder9 = $ViewHolder@34bf5569
20.647 12491-12491/? [getView()] - viewHolder10 = $ViewHolder@20eedf25
20.650 12491-12491/? [getView()] - viewHolder11 = $ViewHolder@27d348a1
20.653 12491-12491/? [getView()] - viewHolder12 = $ViewHolder@2e3acddd
20.656 12491-12491/? [getView()] - viewHolder13 = $ViewHolder@3290cc9e
40.937 12491-12491/: [MainActivity$1:48 onScrollStateChanged()] - scroll
44.879 12491-12491/: [MainActivity$1:52 onScrollStateChanged()] - fling
44.994 12491-12491/: [MainActivity$1:44 onScrollStateChanged()] - idle
靜態(tài)內(nèi)部類的實例地址都是不一樣的寺酪,所以并沒有優(yōu)化 ViewHolder 的個數(shù)。然而實際情況是因為 convertView 的復用機制替劈,convertView 不會太多寄雀,一般情況下=ListView 顯示的 item 個數(shù) + 1*viewTypeCount。所以 ViewHolder 的個數(shù)也不會太多陨献。所以沒有優(yōu)化的意義盒犹。