一談起ListView,我想大家都不陌生。而且最近該控制項特別紅,像QQ,人人和新浪用戶端裡都有它的影子。
其實實現ListView非常的簡單。
我想大家都用過各種各樣的控制項,比如說一個最簡單的TextView,我們都是在布局檔案裡加入TextView標籤,然後在Activity裡通過findViewById(int id)方法得到該對象的引用,最後調用TextView類的setText(CharSequence s)方法設定該控制項的值。
同樣,對於ListView,我們先在布局檔案裡這樣添加標籤:
[html]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ListView
android:id="@+id/mylist"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
</LinearLayout>
有了布局檔案,然後我們在Activity裡通過findViewById(int id)方法得到ListView對象的引用
[java]
ListView listView = (ListView) findViewById(R.id.mylist);
有了控制項還不行,我們可以把ListView看作是一個可以伸縮的容器,我們需要往裡新增內容。作為資料轉送的橋樑,Adapter封裝了所需的資料,通過調用ListView的方法setAdapter(Adapter a)將資料繫結到ListView中,這樣螢幕上就有資料顯示了。
Adapter是一個介面,定義了許多規範。Android提供了實現該介面的一些方便的類,如ArrayAdapter,CursorAdapter。下面以ArrayAdapter類為例講解如何建立一個Adapter。
[java]
String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
"Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
"Linux", "OS/2" };
// First paramenter - Context
// Second parameter - Layout for the row
// Third parameter - ID of the TextView to which the data is written
// Forth - the Array of data
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, android.R.id.text1, values);
是不是很簡單?在上面的那個構造方法中,一共有四個參數,第一個參數很簡單,就是一個內容物件Context,第二個參數是描述每一行的布局,這裡使用的是Android內建的一個簡單布局,第三個參數是該View的id,最後一個是加入的數組。
上面的ArrayAdapter只能在每一行顯示一些文本資訊,如果想豐富一下,比如增加圖片等,就需要繼承該類,實現自己的自訂類。
[java
public class MySimpleArrayAdapter extends ArrayAdapter<String> {
private final Context context;
private final String[] values;
public MySimpleArrayAdapter(Context context, String[] values) {
super(context, R.layout.rowlayout, values);
this.context = context;
this.values = values;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.rowlayout, parent, false);
TextView textView = (TextView) rowView.findViewById(R.id.label);
ImageView imageView = (ImageView) rowView.findViewById(R.id.icon);
textView.setText(values[position]);
String s = values[position];
imageView.setImageResource(R.drawable.ok);
return rowView;
}
}
繼承該類最關鍵的就是複寫getView()方法,因為ListView是通過該方法得到視圖然後顯示在螢幕上的。在本方法中,我們自訂了一個XML布局檔案,裡面有TextView標籤和ImageView標籤,分別用來顯示文字和圖片資訊,這裡是先得到系統服務LayoutInflater,調用該方法的inflate得到該布局的View,最後通過findViewById()方法擷取TextView和ImageView的對象引用,再給它們賦值返回就結束了。
但是本章的討論不是講解如何?ListView,但是考慮到有些沒有接觸過ListView的同志,就大概寫了一點demo,同時以此例子為引子,指出該方法存在的一些效能問題。
由於通過調用LayoutInflater的inflate方法獲得的View,其實會產生新的對象,建立對象是很耗時和資源的(記憶體),另外調用getViewById()方法也會相對耗時和耗資源,雖然其強度不如前者。
所以Android決定,如果代表每一行的View不可見(向下滑動,上面的View被遮住了,即為不可見),那麼它將允許getView方法通過convertView複用該View,達到提升效能的目的。
我們先來看下ArrayAdapter是如何進行最佳化的。
[java]
public View getView(int position, View convertView, ViewGroup parent) {
return createViewFromResource(position, convertView, parent, mResource);
}
private View createViewFromResource(int position, View convertView, ViewGroup parent,
int resource) {
View view;
TextView text;
if (convertView == null) {
view = mInflater.inflate(resource, parent, false);
} else {
view = convertView;
}
try {
if (mFieldId == 0) {
// If no custom field is assigned, assume the whole resource is a TextView
text = (TextView) view;
} else {
// Otherwise, find the TextView field within the layout
text = (TextView) view.findViewById(mFieldId);
}
} catch (ClassCastException e) {
Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
throw new IllegalStateException(
"ArrayAdapter requires the resource ID to be a TextView", e);
}
T item = getItem(position);
if (item instanceof CharSequence) {
text.setText((CharSequence)item);
} else {
text.setText(item.toString());
}
return view;
}
該方法首先判斷傳給該方法的convertView是否為null,如果為null,那麼就調用耗時的inflate方法建立View對象,如果不為空白(該convertView是以前inflate過的,只不過被遮住了),就複用該對象,達到了部分最佳化。
上面之所以說是部分最佳化,是因為只考慮了最佳化inflate帶來的負載,而忽略了getViewById()方法引起的效能問題。解決辦法是在自訂Adapter類裡引進靜態內部類ViewHolder,如其名字,該類裡存放我們需要顯示每一行的所有控制項,比如TextView,ImageView等。當convertView為空白時,我們建立布局檔案的View,然後分別得到布局裡的各種控制項,再把它們存放在ViewHolder類裡,最後再調用convertView的 setTag(Object o)方法把該類綁定到該類裡。
[java]
public class MyPerformanceArrayAdapter extends ArrayAdapter<String> {
private final Activity context;
private final String[] names;
static class ViewHolder {
public TextView text;
public ImageView image;
}
public MyPerformanceArrayAdapter(Activity context, String[] names) {
super(context, R.layout.rowlayout, names);
this.context = context;
this.names = names;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView; www.2cto.com
if (rowView == null) {
LayoutInflater inflater = context.getLayoutInflater();
rowView = inflater.inflate(R.layout.rowlayout, null);
ViewHolder viewHolder = new ViewHolder();
viewHolder.text = (TextView) rowView.findViewById(R.id.TextView01);
viewHolder.image = (ImageView) rowView
.findViewById(R.id.ImageView01);
rowView.setTag(viewHolder);
}
ViewHolder holder = (ViewHolder) rowView.getTag();
String s = names[position];
holder.text.setText(s);
if (s.startsWith("Windows7") || s.startsWith("iPhone")
|| s.startsWith("Solaris")) {
holder.image.setImageResource(R.drawable.no);
} else {
holder.image.setImageResource(R.drawable.ok);
}
return rowView;
}
}
根據統計資訊,這樣的最佳化設計,比最初的方法效率上要快15%以上。