ListView絕對可以稱得上是Android中最常用的控件之一,幾乎所有應用程序都會用到它。
由於手機屏幕空間都比較有限,能夠一次性在屏幕上顯示的內容並不多,當我們的程序中有大量的數據需要展示的時候,就可以借助ListView來實現。
----------------------------------------------ListView簡易用法------------------------------------------------------
創建一個ListViewTest項目,讓Android Studio自動創建好活動。
然后修改activity_main.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="match_parent"> <ListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent"> </ListView> </LinearLayout>
引入了ListView后,即使你的ListView沒有內容,可視化編輯器preview仍然會這樣顯示:
接下來修改MainActivity中的代碼,如下所示:
public class MainActivity extends AppCompatActivity { private String[] data = {"肖申克的救贖", "這個殺手不太冷", "霸王別姬", "泰坦尼克號", "瓦力", "三傻大鬧寶萊塢", "放牛班的春天", "千與千尋", "鬼子來了", "星際穿越"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ArrayAdapter<String> adapter = new ArrayAdapter<String>( MainActivity.this, android.R.layout.simple_list_item_1, data ); ListView listView = (ListView) findViewById(R.id.list_view); listView.setAdapter(adapter); } }
直接在MainActivity中定義字符串數組雖然可以達到目的,但是不便於管理。
可以在res目錄下的values文件夾下新建一個array.xml文件,內容如下:
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="moive"> <item>肖申克的救贖</item> <item>這個殺手不太冷</item> <item>霸王別姬</item> <item>泰坦尼克號</item> <item>瓦力</item> <item>三傻大鬧寶萊塢</item> <item>放牛班的春天</item> <item>千與千尋</item> <item>鬼子來了</item> <item>星際穿越</item> </string-array> </resources>
通過Resources對象獲得array.xml的內容:
Resources resourse = this.getResources(); String[] data = resourse.getStringArray(R.array.moive);
最終結果如下:
-----------------------------------------------------適配器的介紹------------------------------------------------------
既然ListView是用於展示大量數據的,這些數據可以是從網上下載的,也可以是從數據庫中讀取的,應該視具體的應用場景來決定。
數組中的數據是無法直接傳遞給ListView的,還需要借助適配器來完成。
Android中提供了很多的適配器的實現類,其中最簡單的就是ArrayAdapter(注意不是SimpleAdapter)。
它可以通過泛型來指定要適配的數據類型,然后在構造函數中把要適配的數據傳入即可。
ArrayAdapter有多個構造函數的重載,根據需要選擇最合適的一種。這里我簡單介紹其中的一種:
new ArrayAdapter<>( Context context , @LayoutRes int resource , String[] objects );
Context:context表示上下文對象,參數:MainActivity.this
@LayoutRes:resource表示ListView子項布局的id,參數:android.R.layout.simple_list_item_1
(android.R.layout.simple_list_item_1這是Android內置的布局文件,里面只有一個TextView,可用於簡單顯示一段文本)
List<String> | String[]:objects中指定要適配的數據。
適配器構建好之后,還需要調用ListView的setAdapter()方法,將構建好的適配器對象傳遞進去,這樣ListView和數據之間的關聯就建立完成了。
--------------------------------------------定制ListView------------------------------------------------------
只能顯示一段文本的ListView實在是太單調了,我們現在就來對ListView的界面進行定制,讓它可以顯示更加豐富的內容。
效果預覽:
素材:
首先定義一個實體類,作為ListView適配器的適配類型。新建類Browser,代碼如下:
public class Browser { private String name; private int icon; public Browser(String name, int icon) { this.name = name; this.icon = icon; } public String getName() { return name; } public int getIcon() { return icon; } }
為了盡量簡單,Browser類中只有兩個字段,name表示瀏覽器的名字,icon對應瀏覽器的圖標。
然后需要為ListView的子項指定一個自定義的布局,在layout目錄下新建browser_item.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"> <ImageView android:id="@+id/browser_icon" android:layout_width="80dp" android:layout_height="80dp" android:layout_marginLeft="20dp" /> <TextView android:id="@+id/browser_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginLeft="120dp" android:textSize="24dp" /> </LinearLayout>
在這個布局中,我們定義了一個ImageView用於顯示瀏覽器圖標,又定義了一個TextView用於顯示瀏覽器的名稱。
為了簡潔,我在ImageView和TextView沒有引入內容。其實引入內容並不會對結果造成影響,而且可以在preview中預覽,以便調整布局。
接下來需要創建一個自定義的適配器,這個適配器繼承自ArrayAdapter,並將泛型指定為Browser類。
新建類BrowserAdapter,代碼如下:
//自定義適配器,繼承自ArrayAdapter public class BrowserAdapter extends ArrayAdapter<Browser>{ //resourceID指定ListView的布局方式 private int resourceID; //重寫BrowserAdapter的構造器 public BrowserAdapter(Context context,int textViewResourceID , List<Browser> objects){ super(context,textViewResourceID,objects); resourceID = textViewResourceID; } //自定義item資源的解析方式 @Override public View getView(int position, View convertView, ViewGroup parent) { //獲取當前Browser實例 Browser browser = getItem(position); //使用LayoutInfater為子項加載傳入的布局 View view = LayoutInflater.from(getContext()).inflate(resourceID,null); ImageView browserIcon = (ImageView)view.findViewById(R.id.browser_icon); TextView browserName = (TextView)view.findViewById(R.id.browser_name); //引入Browser對象的屬性值 browserIcon.setImageResource(browser.getIcon()); browserName.setText(browser.getName()); return view; } }
BrowserAdapter重寫了父類的一組構造函數,用於將上下文、ListView子項布局的id和數據都傳遞進來。另外又重寫了getView()方法,這個方法在每個子項被滾動到屏幕內的時候會被調用。
在getView方法中,首先通過getItem()方法得到當前項的Browser實例,然后使用LayoutInflater來為這個子項加載我們傳入的布局,接着調用View的findViewById()方法分別獲取到ImageView和TextView的實例,並分別調用它們的setImageResource()和setText()方法來設置顯示的圖片和文字,最后將布局返回,自定義適配器就完成了。
下面修改MainActivity中的代碼,如下所示:
public class MainActivity extends AppCompatActivity { //Browser實體集合 private List<Browser> browsers = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化數據 Browser ie = new Browser("IE", R.drawable.ie); Browser chrome = new Browser("Chrome", R.drawable.chrome); Browser firefox = new Browser("Firefox", R.drawable.firefox); Browser saferi = new Browser("Saferi", R.drawable.safari); browsers.add(ie); browsers.add(chrome); browsers.add(firefox); browsers.add(saferi); //初始化適配器 BrowserAdapter adapter = new BrowserAdapter(MainActivity.this, R.layout.browser_item, browsers); ListView listView = (ListView) findViewById(R.id.list_view); listView.setAdapter(adapter); } }
雖然目前我們定制的界面還是很簡單,但是如果你領悟到了訣竅,只要修改browser_item.xml中的內容,就可以定制出各種復雜的界面了。
--------------------------------------------提升ListView的運行效率----------------------------------------------
之所以說ListView這個控件很難用,就是因為它有很多的細節可以優化,其中運行效率就是很重要的一點。
目前我們的ListView運行效率是很低的,因為在BrowserAdapter的getView()方法中每次都將布局重新加載了一遍,當ListView快速滾動的時候這就會成為性能的瓶頸。
getView()中還有一個convertView參數,這個參數用於將之前加載好的布局進行緩存,以便之后可以進行重用。
修改BrowserAdapter中的setView()代碼,如下所示:
public View getView(int position, View convertView, ViewGroup parent) { Browser browser = getItem(position); View view; //convertView為空則加載布局,不為空則重用 if(convertView == null){ view = LayoutInflater.from(getContext()).inflate(resourceID,null); }else{ view = convertView; } ImageView browserIcon = (ImageView)view.findViewById(R.id.browser_icon); TextView browserName = (TextView)view.findViewById(R.id.browser_name); browserIcon.setImageResource(browser.getIcon()); browserName.setText(browser.getName()); return view; }
現在我們在getView()方法中進行了判斷,如果convertView為空,則使用LayoutInflater去加載布局,如果不為空則直接對convertView進行重用。這樣就大大提高了ListView的運行效率,在快速滾動的時候可以表現出更好的性能。
不過,雖然現在已經不會再去重復加載布局,但是每次在getView()方法中還是會調用View的findViewById()方法來獲取一次控件的實例。
我們可以借助一個ViewHolder內部類來對這部分性能進行優化,修改BrowserAdapter中的setView()方法,如下所示:
//自定義適配器,繼承自ArrayAdapter public class BrowserAdapter extends ArrayAdapter<Browser>{ //resourceID指定ListView的布局方式 private int resourceID; //重寫BrowserAdapter的構造器 public BrowserAdapter(Context context,int textViewResourceID , List<Browser> objects){ super(context,textViewResourceID,objects); resourceID = textViewResourceID; } @Override public View getView(int position, View convertView, ViewGroup parent) { Browser browser = getItem(position); View view; ViewHolder viewHolder; if(convertView == null){ view = LayoutInflater.from(getContext()).inflate(resourceID,null); viewHolder = new ViewHolder(); viewHolder.browserIcon = (ImageView)view.findViewById(R.id.browser_icon); viewHolder.browserName = (TextView)view.findViewById(R.id.browser_name); //將ViewHolder存儲在View中 view.setTag(viewHolder); }else { view = convertView; viewHolder = (ViewHolder)view.getTag(); } viewHolder.browserIcon.setImageResource(browser.getIcon()); viewHolder.browserName.setText(browser.getName()); return view; } class ViewHolder{ ImageView browserIcon; TextView browserName; } }
我們新增了一個內部類ViewHolder,用於對控件的實例進行緩存。當convertView為空的時候,創建一個ViewHolder對象,並將控件的實例都存放在ViewHolder里,然后調用View的setTag()方法,將ViewHolder對象存儲在View中。當convertView不為空的時候調用View的getTag()方法,把ViewHolder重新取出。這樣所有的控件的實例都緩存在了ViewHolder里,就沒有必要每次都通過findViewById()方法來獲取控件實例了。
經過這兩步的優化之后,ListView的運行效率完全可以滿足我們的需要了。
----------------------------------------------ListView的點擊事件-----------------------------------------------------
使用setOnItemClickListener()方法來為ListView注冊一個監聽器,當用戶點擊了ListView中的任何一個子項時都會回調onItemClick()方法,在這個方法中可以通過position參數判斷出用戶點擊的是哪一個子項,然后執行相應的程序。
BrowserAdapter adapter = new BrowserAdapter(MainActivity.this, R.layout.browser_item, browsers); ListView listView = (ListView) findViewById(R.id.list_view); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Browser browser = browsers.get(position); Toast.makeText(MainActivity.this,browser.getName().toString(),Toast.LENGTH_SHORT).show(); } }); listView.setAdapter(adapter);