自定義SimpleAdapter
來自: http://www.jcodecraeer.com//a/anzhuokaifa/androidkaifa/2014/0928/1717.html
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);