Translated by:AcerWang 原文出自:customizing-android-listview-items-with-custom-arrayadapter
背景介紹
對於現實世界中的商業移動應用來說,Android的ListView默認的界面外觀不是非常有吸引力。它只是使用了內部的TextView控件,在每個ListView的行(Row)里面傳遞了一個簡單的字符串而已。大多數應用,你會想要創建出富含圖形界面和呈現給用戶視覺體驗良好的應用。幸運地是,ListView 是一個非常強大的控件,由於有可定制的item 布局的幫助,它可以被定制從而輕松地適應你的需求。在本文中,我將向你展示怎樣創建一個定制的ListView Item(有圖標,自定義的header布局)以及怎樣使用定制的ArrayAdapter將他們聯系起來。我也會向你展示一些性能優化的小方法來優化你的ListView控件的內存占用。下面用一個例子來展示:
圖1. 天氣圖 圖2. 布局結構圖
一、項目布局
在Eclipse中,創建一個新的Android項目,使用默認的Activity和main.xml布局文件。在main.xml文件中,聲明一個ListView控件。
main.xml文件:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout 3 xmlns:android=http://schemas.android.com/apk/res/android 4 android:orientation="vertical" 5 android:layout_width="fill_parent" 6 android:layout_height="fill_parent" 7 android:background="#FFFFFF"> 8 9 <ListView 10 android:id="@+id/listView1" 11 android:layout_width="fill_parent" 12 android:layout_height="fill_parent" /> 13 </LinearLayout>
上面的代碼,使用了簡單的線性布局方式,內部垂直排列。聲明了一個ListView,占據整個父容器,他的android.layout_width和android.layout_width的屬性都為fill_parent。ListView有一個唯一的id:listView1,在MainActivity中將用來引用ListView控件。
為了創建定制的header,先在你的工程中創建一個新的xml布局文件:listview_header_row.xml,在里面聲明一個TextView控件,屬性值見下面的代碼。將會創建出一個白色字體,藍色背景的header。
1 listview_header_row.xml文件: 2 <?xml version="1.0" encoding="utf-8"?> 3 <LinearLayout 4 xmlns:android="http://schemas.android.com/apk/res/android" 5 android:orientation="horizontal" 6 android:layout_width="fill_parent" 7 android:layout_height="fill_parent"> 8 9 <TextView android:id="@+id/txtHeader" 10 android:layout_width="fill_parent" 11 android:layout_height="fill_parent" 12 android:gravity="center_vertical" 13 android:layout_alignParentTop="true" 14 android:layout_alignParentBottom="true" 15 android:textStyle="bold" 16 android:textSize="22dp" 17 android:textColor="#FFFFFF" 18 android:padding="10dp" 19 android:text="Weather Photos" 20 android:background="#336699" /> 21 22 </LinearLayout>
為了創建定制的ListView的行樣式,先在你的工程中創建另一個xml布局文件:listview_item_row.xml。Android 會將這個文件的內容傳遞給每個ListView的item,你將可以自由的聲明任何你想添加進里面的控件。本文中,我使用了一個ImageView來顯示天氣圖標和一個TextView來顯示該條item的主題。下面是listview_item_row.xml文件的代碼:
1 listview_item_row.xml文件: 2 <?xml version="1.0" encoding="utf-8"?> 3 <LinearLayout 4 xmlns:android="http://schemas.android.com/apk/res/android" 5 android:orientation="horizontal" 6 android:layout_width="fill_parent" 7 android:layout_height="fill_parent" 8 android:padding="10dp"> 9 10 <ImageView android:id="@+id/imgIcon" 11 android:layout_width="wrap_content" 12 android:layout_height="fill_parent" 13 android:gravity="center_vertical" 14 android:layout_alignParentTop="true" 15 android:layout_alignParentBottom="true" 16 android:layout_marginRight="15dp" 17 android:layout_marginTop="5dp" 18 android:layout_marginBottom="5dp" /> 19 20 <TextView android:id="@+id/txtTitle" 21 android:layout_width="fill_parent" 22 android:layout_height="fill_parent" 23 android:gravity="center_vertical" 24 android:layout_alignParentTop="true" 25 android:layout_alignParentBottom="true" 26 android:textStyle="bold" 27 android:textSize="22dp" 28 android:textColor="#000000" 29 android:layout_marginTop="5dp" 30 android:layout_marginBottom="5dp" /> 31 32 </LinearLayout>
本文中,我下載了一些32 X 32像素的PNG格式的圖標。如果你願意,你也可以使用你自己的圖標。准備好你的圖標,放到你工程的drawable-mdpi文件目錄下。接下來,在工程中新建一個java類,命名為Weather.java,這個類將用於創建一個定制的ArrayAdapter來綁定對象到ListView中。下面是Weather.java文件的代碼,它有兩個簡單的屬性icon和title,一個普通的構造函數用於初始化屬性。
二、項目程序開發
為了方便大家理解,我將程序結構流程畫出來:
圖3. 重要對象關系結構
1 Weather.java文件: 2 public class Weather { 3 public int icon; 4 public String title; 5 public Weather(){ 6 super(); 7 } 8 9 public Weather(int icon, String title) { 10 super(); 11 this.icon = icon; 12 this.title = title; 13 } 14 }
注意,上面listview_item_row.xml文件有兩個View,對應於Weather類的兩個屬性。Weather類的屬性值將被顯示到這兩個View中。為了將這兩個View連接起來,你需要創建一個定制的ArrayAdapter,它繼承了Android的ArrayAdapter類,並重寫了getView方法。添加一個新的java類到你的工程中,命名為WeatherAdapter,具體的實現代碼如下:
1 WeatherAdapter.java文件: 2 public class WeatherAdapter extends ArrayAdapter<Weather>{ 3 4 Context context; 5 int layoutResourceId; 6 Weather data[] = null; 7 8 public WeatherAdapter(Context context, int layoutResourceId, Weather[] data) { 9 super(context, layoutResourceId, data); 10 this.layoutResourceId = layoutResourceId; 11 this.context = context; 12 this.data = data; 13 } 14 15 @Override 16 public View getView(int position, View convertView, ViewGroup parent) { 17 View row = convertView; 18 WeatherHolder holder = null; 19 20 if(row == null) 21 { 22 LayoutInflater inflater = ((Activity)context).getLayoutInflater(); 23 row = inflater.inflate(layoutResourceId, parent, false); 24 25 holder = new WeatherHolder(); 26 holder.imgIcon = (ImageView)row.findViewById(R.id.imgIcon); 27 holder.txtTitle = (TextView)row.findViewById(R.id.txtTitle); 28 29 row.setTag(holder); 30 } 31 else 32 { 33 holder = (WeatherHolder)row.getTag(); 34 } 35 36 Weather weather = data[position]; 37 holder.txtTitle.setText(weather.title); 38 holder.imgIcon.setImageResource(weather.icon); 39 40 return row; 41 } 42 43 static class WeatherHolder 44 { 45 ImageView imgIcon; 46 TextView txtTitle; 47 } 48 }
在上面的代碼中,第一個比較重要的是類的構造函數有三個參數,第一個參數是Context對象(我們可以傳遞當前使用WeatherAdapter類的activity對象的引用,即MainActivity.this對象);第二個參數是resource的id(它是我們想用來呈現每個ListView的item的布局文件的id),在本文中我將傳遞我創建的listview_item_row.xml布局文件的id;第三個參數是一個Weather對象的數組,用於為Adapter適配器提供顯示數據的數據源。
ArrayAdapter的getView方法被重寫了。這個方法將被ListView每個 item項調用來創建視圖View,它們的屬性是我們設置的。getView方法也使用了一個臨時的holder類(在WeatherAdapter類內部聲明的內部類),這個類將被用於緩存ImageView和TextView,以便它們能夠被ListView中的每行重用,這也會為我們帶來巨大的性能的提升,由於我們不斷地訪問兩個相同的views(ImageView和TextView)的屬性,我們不必為每個ListView的Item查找這兩個控件。上面的代碼也是用了Android內置的LayoutInflator來解析xml布局文件(用於動態加載xml布局文件,以便能夠查找其中的內容)。
最后一點代碼是我們應用的MainActivity。里面,我們使用了所有上面聲明的對象。下面是MainActivity.java文件的代碼:
1 MainActivity.java文件: 2 public class MainActivity extends Activity { 3 4 private ListView listView1; 5 6 @Override 7 public void onCreate(Bundle savedInstanceState) { 8 super.onCreate(savedInstanceState); 9 setContentView(R.layout.main); 10 11 Weather weather_data[] = new Weather[] 12 { 13 new Weather(R.drawable.weather_cloudy, "Cloudy"), 14 new Weather(R.drawable.weather_showers, "Showers"), 15 new Weather(R.drawable.weather_snow, "Snow"), 16 new Weather(R.drawable.weather_storm, "Storm"), 17 new Weather(R.drawable.weather_sunny, "Sunny") 18 }; 19 20 WeatherAdapter adapter = new WeatherAdapter(this, 21 R.layout.listview_item_row, weather_data); 22 23 24 listView1 = (ListView)findViewById(R.id.listView1); 25 26 View header = (View)getLayoutInflater().inflate(R.layout.listview_header_row, null); 27 listView1.addHeaderView(header); 28 29 listView1.setAdapter(adapter); 30 }
MainActivity.java文件中有幾個需要解釋下的地方,以便你能更好的理解。首先,我們創建了一個Weather對象的數組,icon和title被作為參數傳遞給了它的構造函數;接下來,WeatherAdapter對象被創建,listview_item_row.xml文件的id和Weather對象數組被傳遞給了它的構造函數。再一次,我們使用了Android的LayoutInflator來解析listview_item_row.xml布局文件。通過ListView的addHeaderView方法設置ListView的header信息。最后,我們傳遞定制的Adapter給ListView的setAdapter方法。到現在就可以構建、運行工程了。如果一切實現正確,你會看到下面的內容。
圖2. 運行效果
最近有一段時間沒寫東西了,真是罪過啊!翻譯之中有不當之處在所難免,大家相互學習。尊重原創,尊重知識,相信分享的力量!