解決方法一,在 TextView 裡逐筆輸出資料,像是 在 Android 裡使用 SQLite 裡的作法,缺點是每一筆資料都只是文字,不是一個 Android 元件,很難運用在互動行為上,譬如說點擊或勾選之類的。
解決方法二,用 coding 的方式逐筆建立 Android 元件,這可以解決互動行為的問題,但缺點是用 coding 做 layout 是盡量避免的方式。
解決方法三,就是 Android List 家族上場的時候了。
簡單的概念就是,在 XML layout 裡使用一種叫做 ListView 的元件,然後在 coding 裡將一堆資料以集合物件的方式塞給 ListView,由 ListView 去做 loop 的動作,就這樣,收工。
簡單的步驟就是這樣:
data > adapter > view > activity要特別注意的是,使用 ListView 需要兩個 XML layout 設定檔,一個設定主頁面,就是定義 ListView 所在的檔案,另一個設定每一筆 ListView 子元件的 layout,前者用在 Activity 裡,後者用在 Adapter 裡。
ListAdapter
Adapter 族譜如下:
ListAdapter 有個兄弟叫做 SpinnerAdapter,這邊先不管他,一般會用到的三個 ListAdapter 實做為 ArrayAdapter、SimpleAdapter 與 SimpleCursorAdapter。
ArrayAdapter
用 array 或 list 作為集合物件的容器,將呼叫 array 或 list 裡物件的 toString() 得到的字串寫到 ListView 裡。
ArrayAdapter 需要的資訊:
- ListView 子元件的 layout resource id,預設限制為 TextView。
ArraayAdapter 提供一種特別的讀取 layout resource 方式,就是指定兩個 id,第一個 id 為 layout id,第二個 id 為前面 layout id 所表示 layout 檔裡的 TextView id。
可以實做 getView(...) 來改用其他 View 元件。 - 一堆用 array 或 list 裝起的資料。
也可以在建立 ArrayAdapter 之後,使用 ArrayAdapter的 API 來維護資料集合。
或者可以用 ArraayAdapter.createFromResource(...) 這個 static method 從 resource 裡讀取 array 來建立 ArraayAdapter。
<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" />Android 提供一些子元件 layout 範本可供使用,位置為 android-sdk\platforms\android-8\data\res\layout。
Activity 程式如下:
public class A_Adapter extends ListActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.setListAdapter(this.createArrayAdapter()); } private ArrayAdapter<String> createArrayAdapter() { String[] array = new String[] { "禮拜一", "禮拜二", "禮拜三", "禮拜四", "禮拜五" }; return new ArrayAdapter<String>(this, R.layout.list_item_a, array); } }結果如下:
SimpleAdapter
前面的 ArrayAdapter 輸出靜態的資料到 ListView,只能透過 toString(...) 來顯示資料,且每筆資料只能顯示一個資訊是最大的缺點,SimpleAdapter 改進這個問題,接受的資料集合容器為 List<Map>,可在 Map 裡任意放入多筆需要的資料。
List 裡的每一個 Map 就是一筆資料,例如一筆通訊錄,每一個 Map 裡可以放進姓名、電話、地址等資訊。
SimpleAdapter 需要的資訊,除了 ArrayAdapter 描述的子元件的 layout resource id 與一堆用 list 裝起的資料外,另外還需要兩樣資訊:
- Map 裡的 key 值。
- Map 裡的 key 值對應到子元件 layout 裡的元件 id。
子元件設定檔 res/layout/list_item_b.xml 如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="10sp" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/txt1" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/txt2" /> </LinearLayout>Activity 程式如下:
public class A_Adapter extends ListActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.setListAdapter(this.createSimpleAdapter()); } private SimpleAdapter createSimpleAdapter() { List<Map<String, String>> data = this.createData(); return new SimpleAdapter(this, data, R.layout.list_item_b, new String[] { "txt1", "txt2" }, new int[] { R.id.txt1, R.id.txt2 }); } private List<Map<String, String>> createData() { List<Map<String, String>> data = new ArrayList<Map<String, String>>(); data.add(this.createMap("禮拜一", "Monday")); data.add(this.createMap("禮拜二", "Tuesday")); data.add(this.createMap("禮拜三", "Wednesday")); data.add(this.createMap("禮拜四", "Thursday")); data.add(this.createMap("禮拜五", "Friday")); return data; } private Map<String, String> createMap(String a, String b) { Map<String, String> map = new HashMap<String, String>(); map.put("txt1", a); map.put("txt2", b); return map; } }結果如下:
SimpleCursorAdapter
前兩個 adapter 都是使用靜態的資料,相對於靜態資料的需求是從資料庫中讀取動態資料後寫到畫面,這就是 SimpleCursorAdapter 做的事情。
使用上的差別就是將靜態的 array 或者 list 換成與資料庫連結的 Cursor 物件(參考 在 Android 裡使用 SQLite),就這樣,其他的都一樣。
ListView
ListView 與 layout managers 一樣,都是繼承自 ViewGroup,用來組合多筆 View 元件成為另一個 View 元件,ListView 的特色為可 scroll 的縱向排列元件容器。
ListActivity
結合 ListAdapater 與 ListView 的地方。
ListActivity 的實做前面已經看過了,這裡要講的是 layout 設定檔。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ListView android:id="@android:id/list" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@android:id/empty" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/empty" /> </LinearLayout>兩個重點:
- ListView 是一定要有的,另外還要提供一個 TextView,用來處理 ListAdapter 沒有任何資料時,顯示無資料的提示訊息用。
- ListView 與 TextView 的 id 命名方式,分別為 list 和 empty,這是在程式裡寫死的,一定得叫這個,另外是 id 的前飾詞,因為這是 Android 定義的,所以得用 @android:id。
回到本文的出發點,為 ListView 加上 Event Listeners。
ListView 的始祖 AdapterView 提供四個 Event Listener:
- setOnClickListener(...):不能用,用了就知道原因,會丟出錯誤訊息「Don't call setOnClickListener for an AdapterView. You probably want setOnItemClickListener instead」。
- setOnItemClickListener(...):點擊觸發。
- setOnItemLongClickListener(...):長點擊觸發。
- setOnItemSelectedListener(...):得用方向鍵(上或下)來觸發。
public class A_Adapter extends ListActivity implements OnItemClickListener, OnItemLongClickListener, OnItemSelectedListener { private static final String TAG = "A_Adapter"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.setListAdapter(this.createSimpleAdapter()); ListView lv = (ListView) this.findViewById(android.R.id.list); lv.setOnItemClickListener(this); lv.setOnItemLongClickListener(this); lv.setOnItemSelectedListener(this); } @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Log.d(A_Adapter.TAG, "onItemSelected: " + view); } @Override public void onNothingSelected(AdapterView<?> parent) { Log.d(A_Adapter.TAG, "onNothingSelected: " + parent); } @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { Log.d(A_Adapter.TAG, "onItemLongClick: " + view); return false; } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Log.d(A_Adapter.TAG, "onItemClick: " + view); } ... }記得不要用 inner class 來實做 listener,每個 inner class 都會額外消耗記憶體。
沒有留言:
張貼留言