SimpleAdapter,跟名字一樣,一個簡單的適配器,既為簡單,就只是被設計來做簡單的應用的,比如靜態數據的綁定,不過仍然有自定義的空間,比如說在每一個ListItem中加一個按鈕並添加響應事件.首先還是先看一下SimpleAdapter的定義吧,直接翻譯下SDK doc 吧:
這是一個簡單的適配器,可以將靜態數據映射到XML文件中定義好的視圖。你可以指定由Map組成的List(比如ArrayList)類型的數據。在ArrayList中的每個條目對應List中的一行。Maps包含每一行的數據。你可以指定一個XML布局以指定每一行的視圖,根據Map中的數據映射關鍵字到指定的視圖。綁定數據到視圖分兩個階段,首先,如果設置了SimpleAdapter.ViewBinder,那么這個設置的ViewBinder的setViewValue(android.view.View, Object, String)將被調用。如果setViewValue的返回值是true,則表示綁定已經完成,將不再調用系統默認的綁定實現。如果返回值為false,視圖將按以下順序綁定數據:
- 如果View實現了Checkable(例如CheckBox),期望綁定值是一個布爾類型。
- TextView.期望綁定值是一個字符串類型,通過調用setViewText(TextView, String)綁定。
- ImageView,期望綁定值是一個資源id或者一個字符串,通過調用setViewImage(ImageView, int) 或 setViewImage(ImageView, String)綁定數據。
如果沒有一個合適的綁定發生將會拋出IllegalStateException。
先看一下構造函數:
public SimpleAdapter (Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
參數:
- context SimpleAdapter關聯的View的運行環境
- data 一個Map組成的List。在列表中的每個條目對應列表中的一行,每一個map中應該包含所有在from參數中指定的鍵
- resource 一個定義列表項的布局文件的資源ID。布局文件將至少應包含那些在to中定義了的ID
- from 一個將被添加到Map映射上的鍵名
- to 將綁定數據的視圖的ID,跟from參數對應,這些應該全是TextView
舉個例子:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ListView lv = (ListView) findViewById(R.id.listView1);
String[] from = { "Text", "Button" };
int[] to = { R.id.text, R.id.button };
List<Map<String, ?>> list = new ArrayList<Map<String, ?>>();
for (int i = 0; i < 10; i++) {
Map<String, String> m = new HashMap<String, String>();
m.put("Text", "Text" + i);
m.put("Button", "Button" + i);
list.add(m);
}
SimpleAdapter adapter = new SimpleAdapter(this, list, R.layout.listitem, from, to);
lv.setAdapter(adapter);
}
listitem.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<TextView
android:layout_width="wrap_content"
android:id="@+id/text"
android:layout_height="wrap_content"
android:layout_weight="1" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
ListView中的每一項都包含一個TextView跟一個Button,在SimpleAdapter的構造函數中,我們指定了要綁定的數據:list, list是一個由Map組成的ArrayList, Map的作用就是連同后面的from, to參數定義數據是如何綁定的,在上面的例子中
String[] from = { "Text", "Button" };
int[] to = { R.id.text, R.id.button };
而在for循環中每個map都put進了兩個鍵值對,鍵名跟from中定義的一一對應,這就表示對於ListView中的每一項,依次尋找在to參數中定義的資源ID,根據這個資源ID在to參數數組中的位置,找到from參數中對應位置的值,以這個值為鍵,在list中的相應項(一個Map)中以這個值為鍵取出這個鍵對應的值綁定到這個資源ID對應的視圖中.
在上面的例子中每一個ListItem都包含一個TextView與一個Button,但程序運行起來后會發現,按鈕可以點擊,而ListItem卻無法點擊,而且沒有對每一個Button關聯響應事件,ListItem無法點擊是因為按鈕搶占了ListItem的焦點,在listitem.xml而已文件中對LinearLayout加上一個屬性就可解決問題:
android:descendantFocusability="blocksDescendants"
下面的問題就是Button的響應事件了.
我的們下SimpleAdaper的源碼會發現,數據的綁定是能過一個叫bindView的函數實現的
private void bindView(int position, View view) {
final Map dataSet = mData.get(position);
if (dataSet == null) {
return;
}
final ViewBinder binder = mViewBinder;
final String[] from = mFrom;
final int[] to = mTo;
final int count = to.length;
for (int i = 0; i < count; i++) {
final View v = view.findViewById(to[i]);
if (v != null) {
final Object data = dataSet.get(from[i]);
String text = data == null ? "" : data.toString();
if (text == null) {
text = "";
}
boolean bound = false;
if (binder != null) {
bound = binder.setViewValue(v, data, text);
}
if (!bound) {
if (v instanceof Checkable) {
if (data instanceof Boolean) {
((Checkable) v).setChecked((Boolean) data);
} else if (v instanceof TextView) {
// Note: keep the instanceof TextView check at the bottom of these
// ifs since a lot of views are TextViews (e.g. CheckBoxes).
setViewText((TextView) v, text);
} else {
throw new IllegalStateException(v.getClass().getName() +
" should be bound to a Boolean, not a " +
(data == null ? "<unknown type>" : data.getClass()));
}
} else if (v instanceof TextView) {
// Note: keep the instanceof TextView check at the bottom of these
// ifs since a lot of views are TextViews (e.g. CheckBoxes).
setViewText((TextView) v, text);
} else if (v instanceof ImageView) {
if (data instanceof Integer) {
setViewImage((ImageView) v, (Integer) data);
} else {
setViewImage((ImageView) v, text);
}
} else {
throw new IllegalStateException(v.getClass().getName() + " is not a " +
" view that can be bounds by this SimpleAdapter");
}
}
}
}
}
其流程大致是,首先檢查SimpleAdapter有沒有指定SimpleAdapter.ViewBinder,如果指定了就調用其setViewValue方法, SimpleAdapter.ViewBinder是一個接口,也只有這一個方法,如果ViewBinder返回true表示我們已經完成了對這個View的數據綁定,就不再調用系統默認的實現,當然我們也可以設置一個ViewBinder添加一些功能后通過返回false再讓系統綁定數據,比如對按鈕添加響應事件,而按鈕上的文字由默認實現綁定.通過看bindView的實現就可以明白開始時所說的綁定順序了:Checkable,TextView,ImageView.
我們對ListItem的自定義是通過對SimpleAdapter設置ViewBinder來實現的
SimpleAdapter.ViewBinder binder = new SimpleAdapter.ViewBinder() {
@Override
public boolean setViewValue(View view, Object data, String textRepresentation) {
if (view instanceof Button) {
final View button = view;
// button.setBackgroundDrawable(getResources().getDrawable(R.drawable.ic_launcher));
view.setOnClickListener(new OnClickListener() {
LinearLayout listItem = (LinearLayout) button.getParent();
TextView tv = (TextView) listItem.findViewById(R.id.text);
@Override
public void onClick(View v) {
Toast.makeText(AdapterDemoActivity.this, tv.getText(), Toast.LENGTH_SHORT).show();
}
});
return false;
}
return false;
}
};
adapter.setViewBinder(binder);
系統對每一個view調用binder的setViewValue(此例中是R.id.text和R.id.button,一個TextView與一個Button),我們首先檢測這個view是不是一個Button,如果是的話就關聯點擊事件,可能通過getParent()函數取得parentView以找到這個view的兄弟view,比如這個例子中的實現就是點擊Button后輸出這個Button所在的ListItem中的TextView上的文字.
在setViewValue中可以完全自定義我們的實現,比如在Button后加一個TextView,當然可以加任何View,但這樣做沒任何意義,當你需要這樣做時你不需要用SimpleAdater而應該用BaseAdapter:
SimpleAdapter.ViewBinder binder = new SimpleAdapter.ViewBinder() {
@Override
public boolean setViewValue(View view, Object data, String textRepresentation) {
if (view instanceof Button) {
final View button = view;
LinearLayout listItem = (LinearLayout) button.getParent();
TextView textView = new TextView(AdapterDemoActivity.this);
textView.setText("AA");
listItem.addView(textView,new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
return false;
}
return false;
}
};
adapter.setViewBinder(binder);