一、ListView與Adapter的關系
ListView是Android開發過程中較為常見的組件之一,它將數據以列表的形式展現出來。一般而言,一個ListView由以下三個元素組成:
1、View,用於展示列表,通常是一個xml所指定的。大家都知道Android的界面基本上是由xml文件負責完成的,所以ListView的界面也理所應當的使用了xml定義。例如在ListView中經常用到的“android.R.layout.simple_list_item”等, 就是Android系統內部定義好的一個xml文件。
2、適配器,用來將不同的數據映射到View上。不同的數據對應不同的適配器,如BaseAdapter、ArrayAdapter、CursorAdapter、SimpleAdapter等, 他們能夠將數組、指針指向的數據、Map等數據映射到View上。也正是由於適配器的存在,使得ListView的使用相當靈活,經過適配器的處理后,在 view看來所有的數據映射過來都是一樣的。
3、數據,具體的來映射數據和資源,可以是字符串,圖片等。通過適配器,這些數據將會被實現到 ListView上。所有的數據和資源要顯示到ListView上都通過適配器來完成。
系統已有的適配器可以將基本的數據顯示到ListView上,如:數組,Cursor指向的數據,Map里的數據。但是在實際開發中這些系統已實現的適配器,有時不能滿足我們的需求。而且系統自帶的含有多選功能ListView在實際使用過程中會有一些問題。要實現復雜的ListView可以通過繼承ListView並重寫相應的方法完成,同時也可以通過繼承BaseAdapter來實現。
二、ListView繪制流程
public abstract class BaseAdapter——抽象類,繼承它需要實現較多的方法,所以就具有較高的靈活性,實現了ListAdapter和SpinnerAdapter。
BaseAdapter需要重寫的方法:
getCount(),
getItem(int position),
getItemId(int position),
getView(int position, View convertView, ViewGroup parent)
ListView在開始繪制的時候,系統首先調用getCount()函數,根據他的返回值得到 listView的長度,然后根據這個長度,調用getView()逐一繪制每一行。如果你的 getCount()返回值是0的話,列表將不顯示同樣return 1,就只顯示一行。
系統顯示列表時,首先實例化一個適配器(這里將實例化自定義的適配器)。當手動完成適配時,必須手動映射數據,這需要重寫getView()方法。系統在繪制列表的每一行的時候將調用此方法。getView()有三個參數,position表示將顯示的是第幾行,covertView是從布局文件中inflate來的布局。我們用LayoutInflater的方法將定義好的item.xml文件提取成View實例用來顯示。然后將xml文件中的各個組件實例化(簡單的findViewById()方法)。這樣便可以將數據對應到各個組件上了。但是按鈕為了響應點擊事件,需要為它添加點擊監聽器,這樣就能捕獲點擊事件。至此一個自定義的listView就完成了,現在讓我們回過頭從新審視這個過程。系統要繪制ListView了,他首先獲得要繪制的這個列表的長度,然后開始繪制第一行,怎么繪制呢?調用getView()函數。在這個函數里面首先獲得一個View(實際上是一個 ViewGroup),然后再實例並設置各個組件,顯示之。好了,繪制完這一行了。那再繪制下一行,直到繪完為止。
三、ListView優化
Adapter的作用就是ListView界面與數據之間的橋梁,當列表里的每一項顯示到頁面時,都會調用Adapter的getView方法返回一個View。在我們的列表有1000000項時會占用極大的系統資源。先看看下面的代碼:
public View getView(int position, View convertView, ViewGroup parent) { View item = mInflater.inflate(R.layout.list_item_icon_text, null); ((TextView) item.findViewById(R.id.text)).setText(DATA[position]); ((ImageView) item.findViewById(R.id.icon)).setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2); return item; }
如果超過1000000項時,后果不堪設想!您可千萬別這么寫!我們再來看看下面的代碼:
public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate(R.layout.item, null); } ((TextView) convertView.findViewById(R.id.text)).setText(DATA[position]); ((ImageView) convertView.findViewById(R.id.icon)).setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2); return convertView; }
怎么樣,上面的代碼是不是好了很多?系統將會減少創建很多View。性能得到了很大的提升。還有沒有優化的方法呢? 答案是肯定的,采用ViewHolder模式:
class ChatListAdapter extends BaseAdapter { static class ViewHolder { TextView text; ImageView icon; } public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = mInflater.inflate(R.layout.list_item_icon_text, null); holder = new ViewHolder(); holder.text = (TextView) convertView.findViewById(R.id.text); holder.icon = (ImageView) convertView.findViewById(R.id.icon); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.text.setText(DATA[position]); holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2); return convertView; } }
怎么樣?會不會又給您的系統帶來很大的提升呢?看看下面三種方式的性能對比圖您就知道了!
四、總結:用ViewHolder模式優化ListView
ViewHolder的作用:優化顯示效率,即之前顯示過的不用再從布局文件讀取,直接從緩存中讀取。可以看到它只是一個靜態類,它的作用就在於減少不必要的調用findViewById。完整的官方例子中convertView 也是避免inflating View。然后把對底下的控件引用存在ViewHolder里面,再用convertView.setTag(holder)把它放在view里,下次就可以用(ViewHolder) convertView.getTag()直接取了。
這個ViewHolder到底是什么呢?我們可以在官方sample看到這段代碼