8.自定義ListView中的行
8.1 問題
應用程式需要自定義ListView中各行的外觀。
8.2 解決方案
(API Level 1)
建立一個自定義的XML佈局,將其傳遞給某個常見的介面卡,或者擴充套件你自己的介面卡,然後用自定義的狀態Drawable覆蓋背景和選中狀態下的行。
8.3 實現機制
1.簡單的自定義
如果需要簡單,那麼建立一個佈局,連線到已有的ListAdapter進行填充;我們以ArrayAdapter為例加以介紹。ArrayAdapter接受的引數是自定義的佈局資源和用於使用資料填充該佈局的TextView控制元件的ID。讓我們建立一些用作背景的自定義的Drawable和一個滿足這些需求的佈局(參見以下三段程式碼)。
res/drawable/row_background_default.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:startColor="#EEEFEF" android:endColor="#989898" android:type="linear" android:angle="270"/> </shape>
res/drawable/row_background_pressed.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:startColor="#0B8CF2" android:endColor="#0661E5" android:type="linear" android:angle="270"/> </shape>
res/drawable/row_background.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true" android:drawable="@drawable/row_background_pressed" /> <item android:drawable="@drawable/row_background_default"/> </selector>
以下程式碼是一個自定義佈局,其中的文字是居中排列的,而不是左對齊的。
res/layout/custom_row.xml
<?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="wrap_content" android:padding = "10dip" android:background = "@drawable/row_background"> <TextView android:id = "@+id/line1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity = "center"/> </LinearLayout>
這個佈局的背景是自定義的漸變狀態列表,在這個列表中為每一行設定了預設狀態和按下狀態的外觀。我們已經定義好一個能與ArrayAdapter所期望的行為契合的佈局,現在可以建立一個Activity,呼叫這個列表,不必再進行其他任何自定義(參見以下程式碼)。
使用了自定義行佈局的Activity
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ListView list = new ListView(this); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.custom_row, R.id.line1, new String[] {"Bill","Tom","Sally","Jenny"}); list.setAdapter(adapter); setContentView(list); }
2.適應更復雜的選項
有時自定義列表中的行意味著要擴充套件ListAdapter。常見的情況是要在一行中放入多項資料或者有些資料並不是文字。在這個示例中,我們再次用自定義的Drawable作為背景,不過這裡的佈局會變得更有意思(參見以下程式碼)。
修改後的res/layout/custom_row.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="10dip"> <ImageView android:id="@+id/leftimage" android:layout_width="32dip" android:layout_height="32dip"/> <ImageView android:id="@+id/rightimage" android:layout_width="32dip" android:layout_height="32dip" android:layout_alignParentRight="true" /> <TextView android:id="@+id/line1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_toLeftOf="@id/rightimage" android:layout_toRightOf="@id/leftimage" android:layout_centerVertical="true" android:gravity="center_horizontal"/> </RelativeLayout>
這個佈局中同樣有一個居中對齊的TextView,但它的每條邊都是一個ImageView。要將這個佈局應用到ListView,我們需要擴充套件SDK中的某個ListAdapter。具體擴充套件哪個ListAdapter取決於要在列表中呈現的資料來源。如果資料只是簡單的字串陣列,擴充套件ArrayAdapter就足夠了。如果資料更復雜些,就有可能需要擴充套件抽象的BaseAdapter。需要擴充套件的方法只有getView(),這個方法負責控制列表中每一行的顯示方式。
在這個示例中,資料只是簡單的字串陣列,所以只需要簡單擴充套件ArrayAdapter即可(參見以下程式碼):
顯示新佈局的Activity和自定義的ListAdapter
public class MyActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ListView list = new ListView(this); setContentView(list); CustomAdapter adapter = new CustomAdapter(this, R.layout.custom_row, R.id.line1, new String[] {"Bill","Tom","Sally","Jenny"}); list.setAdapter(adapter); } private static class CustomAdapter extends ArrayAdapter<String> { public CustomAdapter(Context context, int layout, int resId, String[] items) { //呼叫ArrayAdapter的實現 super(context, layout, resId, items); } @Override public View getView(int position, View convertView, ViewGroup parent) { View row = convertView; //如果某行沒有被回收的話,填充該行 if(row == null) { row = LayoutInflater.from(getContext()).inflate(R.layout.custom_row, parent, false); } String item = getItem(position); ImageView left = (ImageView)row.findViewById(R.id.leftimage); ImageView right = (ImageView)row.findViewById(R.id.rightimage); TextView text = (TextView)row.findViewById(R.id.line1); left.setImageResource(R.drawable.ic_launcher); right.setImageResource(R.drawable.ic_launcher); text.setText(item); return row; } } }
注意,這裡用來建立介面卡例項的建構函式與前面使用的建構函式相同,因為它們都繼承自ArrayAdapter。因為過載了介面卡的檢視顯示機制,所有在這裡將R.layout.custom_row和R.id.line1傳遞給建構函式只是因為這是建構函式的必要引數;在這個示例中,這兩個引數是不起作用的。
現在,當ListView要顯示一行內容時,就會呼叫其介面卡的getView()方法。我們已經這個方法進行了自定義,以便能控制每一行內容的返回方式。getView()方法有一個名為convertView的引數,這個引數對效能有重要影響。將XML檔案轉換為佈局是一個代價很高的操作,為了儘量減少對其系統的影響,在滾動時ListView會回收檢視。如果回收的檢視還能重用,就作為convertView傳遞給getView()方法。所以,為了確保列表滾動的響應速度,要儘可能重用檢視,而不是生成新的檢視。
在這個示例中,呼叫getItem()得到列表(字串陣列)中位置的當前值,然後將TextView設為這個值應用於該行。我們還可以給每行設定圖片來凸顯資料,不過這裡只使用了應用程式的圖示。