標籤:
之所以說 ListView 這個控制項很難用,就是因為它有很多的細節可以最佳化,其中運行效率 就是很重要的一點。目前我們 ListView 的運行效率是很低的,因為在 FruitAdapter 的 getView() 方法中每次都將布局重新載入了一遍,當 ListView 快速滾動的時候這就會成為效能的瓶頸。
仔細觀察,getView()方法中還有一個 convertView 參數,這個參數用於將之前載入好的 布局進行緩衝,以便之後可以進行重用。修改 FruitAdapter 中的代碼,如下所示:
public class FruitAdapter extends ArrayAdapter<Fruit> {
……
@Override
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;
}
}
可以看到,現在我們在 getView()方法中進行了判斷,如果 convertView 為空白,則使用 LayoutInflater 去載入布局,如果不為空白則直接對 convertView 進行重用。這樣就大大提高了ListView 的運行效率,在快速滾動的時候也可以表現出更好的效能。
不過,目前我們的這份代碼還是可以繼續最佳化的,雖然現在已經不會再重複去載入布局,但是每次在 getView()方法中還是會調用 View 的 findViewById()方法來擷取一次控制項的執行個體。我們可以藉助一個 ViewHolder 來對這部分效能進行最佳化,修改 FruitAdapter 中的代碼,如下所示:
public class FruitAdapter extends ArrayAdapter<Fruit> {
……
@Override
public View getView(int position, View convertView, ViewGroup parent) { Fruitfruit = getItem(position);
View view;
ViewHolder viewHolder;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, null);
viewHolder = new ViewHolder();
viewHolder.fruitImage = (ImageView) view.findViewById
(R.id.fruit_image);
viewHolder.fruitName = (TextView) view.findViewById
(R.id.fruit_name);
view.setTag(viewHolder); // 將ViewHolder儲存在View中
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();// 重新擷取ViewHolder
}viewHolder.fruitImage.setImageResource(fruit.getImageId());viewHolder.fruitName.setText(fruit.getName());
return view;
}
class ViewHolder { ImageView fruitImage;TextView fruitName;
}
}
我們新增了一個內部類 ViewHolder,用於對控制項的執行個體進行緩衝。當 convertView 為空白 的時候,建立一個 ViewHolder 對象,並將控制項的執行個體都存放在 ViewHolder 裡,然後調用 View 的 setTag()方法,將 ViewHolder Object Storage Service在 View 中。當 convertView 不為空白的時候則調用 View 的 getTag()方法,把 ViewHolder 重新取出。這樣所有控制項的執行個體都緩衝在了 ViewHolder裡,就沒有必要每次都通過 findViewById()方法來擷取控制項執行個體了。
通過這兩步的最佳化之後,我們 ListView 的運行效率就已經非常不錯了。
3.5.4 ListView 的點擊事件
話說回來,ListView 的滾動畢竟只是滿足了我們視覺上的效果,可是如果 ListView 中的 子項不能點擊的話,這個控制項就沒有什麼實際的用途了。因此,本小節中我們就來學習一下 ListView 如何才能響應使用者的點擊事件。
修改 MainActivity 中的代碼,如下所示:
public class MainActivity extends Activity {
private List<Fruit> fruitList = newArrayList<Fruit>();
@Override
protected voidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); initFruits();
FruitAdapter adapter = newFruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
ListView listView = (ListView)findViewById(R.id.list_view); listView.setAdapter(adapter);listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public voidonItemClick(AdapterView<?> parent, View view, int position, long id) {
Fruit fruit = fruitList.get(position);Toast.makeText(MainActivity.this, fruit.getName(),
Toast.LENGTH_SHORT).show();
}
});
}
……
}
可以看到,我們使用了 setOnItemClickListener()方法來為 ListView 註冊了一個監聽器,當使用者點擊了 ListView 中的任何一個子項時就會回調 onItemClick()方法,在這個方法中可以通過position 參數判斷出使用者點擊的是哪一個子項,然後擷取到相應的水果,並通過 Toast 將水果的名字顯示出來。
重新運行程式,並點擊一下西瓜,效果 3.31 所示。
圖 3.31
android:提升 ListView 的運行效率