Adapter介紹
Adapter是将資料綁定到UI界面上的橋接類。Adapter負責建立顯示每個項目的子View和提供對下層資料的通路。
支援Adapter綁定的UI控件必須擴充AdapterView抽象類。建立自己的繼承自AdapterView的控件和建立新的Adapter類來綁定它們是可能的。
一些Android提供的Adapter介紹
在多數情況下,你不需要白手建立自己的Adapter。Android提供了一系列Adapter來将資料綁定到UI Widget上。
因為Android負責提供資料和選擇用于顯示每個項目的View,是以Adapter能快速地修改要綁定的控件的外觀和功能。下面的清單顯示了兩個最有用和最通用的本地Adapter:
❑ ArrayAdapter
ArrayAdapter是一個綁定View到一組對象的通用類。預設情況下,ArrayAdapter綁定每個對象的toString值到在layout中預先定義的TextView控件上。可變通的,構造函數允許你使用更加複雜的layout或者通過重寫getView方法來擴充類進而使用TextView的替代物(如ImageView或嵌套的layout)。
❑ SimpleCursorAdapter
SimpleCursorAdapter綁定View到Content Provider查詢傳回的遊标上。指定一個XML layout定義,然後将資料集中的每一列的值綁定到layout中的一個View上。
接下來的章節将深入挖掘這些Adapter類的細節。例子中,提供了綁定資料到ListView上,盡管這個邏輯會和其他一些AdapterView類(如Spinner和Gallery)工作的一樣。
使用Adapter進行資料綁定
将Adapter應用到繼承自AdapterView類上,你需要調用View的setAdapter方法,傳入一個Adapter執行個體,如下面的片段所示:
ArrayList<String> myStringArray = new ArrayList<String>();
ArrayAdapter<String> myAdapterInstance;
int layoutID = android.R.layout.simple_list_item_1;
myAdapterInstance = new ArrayAdapter<String>(this, layoutID, myStringArray);
myListView.setAdapter(myAdapterInstance);
這個片段顯示了最簡單的情況,将數組中的字元串綁定到ListView中用于顯示每個項目的簡單TextView控件上。
接下來的第一個例子顯示了如何綁定一組複雜的對象到ListView上,使用一個自定義的layout。第二個例子顯示了如何使用SimpleCursorAdapter來綁定查詢結果到ListView中的自定義layout上。
在android開發中清單的使用是十分常見的。google對清單的封裝使清單既有顯示傳統文本清單的能力,也有加入了諸如選擇項、複選項等處理事件的能力。這裡寫一些我這幾天對這個問題的了解。
在android的api中,LIST和adapter都被放在了android.widget包内。包内的具體結構我這裡先不展示了,主要側重清單和adapter。adapter的作用就是将要在清單内顯示的資料和清單本身結合起來。清單本身隻完成顯示的作用,其實他就是繼承自VIEWGROUP類。但是他又有一個獨特的函數就是setAdapter()就是完成了view和adapter的結合。adapter如同其本身含義,其實就是一個擴充卡,他可以對要顯示的資料進行統一的封裝,主要是将資料變成view提供給list。
我們先來看看adapter的體系:
public interface Adapter----0層(表示繼承體系中的層次)
public interface ExpandableListAdapter---(無所謂層次因為沒有其他接口繼承實作它)
這是adapter的始祖,其他個性化的adapter均實作它并加入自己的接口。
public interface ListAdapter----1層
public interface SpinnerAdapter----1層
public interface WrapperListAdapter----2層(實作ListAdapter)
以上接口層面上的體系已經完了。可以看出來作為widget view的橋梁adapter其實隻分為2種:ListAdapter和SpinnerAdapter以及ExpandableListAdapter。也就是說所有widget也就是基于list和spinne與ExpandableList三種view形式的。
由于在實際使用時,我們需要将資料加入到Adapter,而以接口形式呈現的adapter無法儲存資料,于是Adapter就轉型為類的模式。
public abstract class BaseAdapter----2層(實作了ListAdapter和SpinnerAdapter)
以抽象類的形式出現構造了類型态下的頂層抽象,包容了List和Spinner
public class ArrayAdapter----3層
public class SimpleAdapter---3層
public class CursorAdapter----3層(CursorAdapter其後還有子類這裡先不探讨)
基本體系有了之後,讓我們看看頂層Adapter裡有哪些方法(隻列舉常用的):
abstract Object getItem(int position)
abstract int getCount()
abstract long getItemId(int position)
abstract int getItemViewType(int position)
abstract View getView(int position,View convertVeiw,ViewGroup parent)
以上是比較重要的方法,ArrayAdapter他們也是重新實作以上方法的。在實際的開發過程中,往往我們要自己做屬于自己的Adapter,以上方法都是需要重新實作的。
ArrayAdapter和SimpleCursorAdapter例子
-
使用ArrayAdapter定制To-Do List
這個例子将擴充To-Do List工程,以一個ToDoItem對象來儲存每一個項目,包含每個項目的建立日期。
你将擴充ArrayAdapter類來綁定一組ToDoItem對象到ListView上,并定制用于顯示每一個ListView項目的layout。
1. 傳回到To-Do List工程。建立一個新的ToDoItem類來儲存任務和任務的建立日期。重寫toString方法來傳回一個項目資料的概要。
package com.paad.todolist; import java.text.SimpleDateFormat; import java.util.Date; public class ToDoItem { String task; Date created; public String getTask() { return task; } public Date getCreated() { return created; } public ToDoItem(String _task) { this(_task, new Date(java.lang.System.currentTimeMillis())); } public ToDoItem(String _task, Date _created) { task = _task; created = _created; } @Override public String toString() { SimpleDateFormat sdf = new SimpleDateFormat(“dd/MM/yy”); String dateString = sdf.format(created); return “(“ + dateString + “) “ + task; } }
2. 打開ToDoList Activity,修改ArrayList和ArrayAdapter變量的類型,儲存ToDoItem對象而不是字元串。然後,你将修改onCreate方法來更新相應的變量初始化。你還需要更新onKeyListener處理函數來支援ToDoItem對象。
- private ArrayList<ToDoItem> todoItems;
- private ListView myListView;
- private EditText myEditText;
- private ArrayAdapter<ToDoItem> aa;
- @Override public void onCreate(Bundle icicle) {
- super.onCreate(icicle); // Inflate your view setContentView(R.layout.main); //
- Get references to UI widgets myListView = (ListView)findViewById(R.id.myListView);
- myEditText = (EditText)findViewById(R.id.myEditText);
- todoItems = new ArrayList<ToDoItem>();
- int resID = R.layout.todolist_item; aa = new ArrayAdapter<ToDoItem>(this, resID, todoItems);
- myListView.setAdapter(aa);
- myEditText.setOnKeyListener(new OnKeyListener() {
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_DOWN)
- if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
- ToDoItem newItem; newItem = new ToDoItem(myEditText.getText().toString()); todoItems.add(0, newItem);
- myEditText.setText(“”);
- aa.notifyDataSetChanged(); cancelAdd(); return true;
- }
- return false;
- }
- });
- registerForContextMenu(myListView);
- }
3. 如果你運作Activity,它将顯示每個to-do項目,如圖5-3所示。
圖5-3
4. 現在,你可以建立一個自定義的layout來顯示每一個to-do項目。修改在第4章中建立的自定義layout,包含另外一個TextView,它将用于顯示每個to-do項目的建立日期。
Java代碼
- import java.text.SimpleDateFormat;
- import android.content.Context;
- import java.util.*;
- import android.view.*;
- import android.widget.*;
- public class ToDoItemAdapter extends ArrayAdapter<ToDoItem> { int resource; public ToDoItemAdapter(Context _context,int _resource, List<ToDoItem> _items) {
- super(_context, _resource, _items); resource = _resource;
- }
- @Override public View getView(int position, View convertView, ViewGroup parent) {
- LinearLayout todoView; ToDoItem item = getItem(position);
- String taskString = item.getTask();
- Date createdDate = item.getCreated();
- SimpleDateFormat sdf = new SimpleDateFormat(“dd/MM/yy”);
- String dateString = sdf.format(createdDate); if (convertView == null) { todoView = new LinearLayout(getContext());
- String inflater = Context.LAYOUT_INFLATER_SERVICE;
- LayoutInflater vi; vi = (LayoutInflater)getContext().getSystemService(inflater); vi.inflate(resource, todoView, true);
- }
- else {
- todoView = (LinearLayout) convertView;
- }
- TextView dateView = (TextView)todoView.findViewById (R.id.rowDate);
- TextView taskView = (TextView)todoView.findViewById(R.id.row); dateView.setText(dateString);
- taskView.setText(taskString);
- return todoView;
- }
- }
5. 建立一個新的類(ToDoItemAdapter),使用指定的ToDoItem變量來擴充一個ArrayAdapter。重寫getView方法來将ToDoItem對象中的task和date屬性指定給第4步建立的layout中的View。
Java代碼
- import java.text.SimpleDateFormat;
- import android.content.Context;
- import java.util.*;
- import android.view.*;
- import android.widget.*;
- public class ToDoItemAdapter extends ArrayAdapter<ToDoItem> { int resource; public ToDoItemAdapter(Context _context,int _resource, List<ToDoItem> _items) {
- super(_context, _resource, _items); resource = _resource;
- }
- @Override public View getView(int position, View convertView, ViewGroup parent) {
- LinearLayout todoView; ToDoItem item = getItem(position);
- String taskString = item.getTask();
- Date createdDate = item.getCreated();
- SimpleDateFormat sdf = new SimpleDateFormat(“dd/MM/yy”);
- String dateString = sdf.format(createdDate); if (convertView == null) { todoView = new LinearLayout(getContext());
- String inflater = Context.LAYOUT_INFLATER_SERVICE;
- LayoutInflater vi; vi = (LayoutInflater)getContext().getSystemService(inflater); vi.inflate(resource, todoView, true);
- }
- else {
- todoView = (LinearLayout) convertView;
- }
- TextView dateView = (TextView)todoView.findViewById (R.id.rowDate);
- TextView taskView = (TextView)todoView.findViewById(R.id.row); dateView.setText(dateString);
- taskView.setText(taskString);
- return todoView;
- }
- }
6. 最後,用ToDoItemAdapter替換ArrayAdapter的定義。
private ToDoItemAdapter aa;
在onCreate中,使用new ToDoItemAdapter來替換ArrayAdapter<String>的執行個體化。
aa = new ToDoItemAdapter(this, resID, todoItems);
7. 如果你運作Activity,它看起來如圖5-4的截圖。
圖5-4
使用SimpleCursorAdapter
SimpleCursorAdapter允許你綁定一個遊标的列到ListView上,并使用自定義的layout顯示每個項目。
SimpleCursorAdapter的建立,需要傳入目前的上下文、一個layout資源,一個遊标和兩個數組:一個包含使用的列的名字,另一個(相同大小)數組包含View中的資源ID,用于顯示相應列的資料值。
下面的架構代碼顯示了如何構造一個SimpleCursorAdapter來顯示聯系人資訊:
Java代碼
- String uriString = “content://contacts/people/”;
- Cursor myCursor = managedQuery(Uri.parse(uriString), null, null, null, null);
- String[] fromColumns = new String[] {
- People.NUMBER,
- eople.NAME
- };
- int[] toLayoutIDs = new int[] {
- R.id.nameTextView,
- R.id.numberTextView
- };
- SimpleCursorAdapter myAdapter; myAdapter = new SimpleCursorAdapter(this,R.layout.simplecursorlayout,myCursor,fromColumns,toLayoutIDs)
SimpleCursorAdapter在本章前面的建立選擇聯系人的例子中使用過。你将在第6章學習到更多關于Content Provider和Cursor的内容,那裡你也将找到更多SimpleCursorAdapter的例子。
首先來看一下Adapter的體系結構:
一個Adapter的對象扮演一個橋梁的角色。這個橋梁連接配接着一個AdapterView和它所包含的資料。Adapter提供了一個通到資料項的途徑。Adapter還負責為在資料集裡的每個資料生項生成一個View。它有一個重要的方法:public abstract View getView (int position,View convertView,ViewGroup parent)。這個方法被setListAdapter(adapter)間接地調用。getView 方法的作用是得到一個View,這個view顯示資料項裡指定位置的資料,你可以或者手動建立一個view或者從一個XML layout中inflate。當這個view被inflated,它的父view(如GridView,ListView等)将要使用預設的 layout參數,除非你用inflate(int,android.view.ViewGroup,boolean)方法來指定一個根view并防止附着在根上。
下面分别講一下它的幾個常見的子類:
ListAdapter接口:繼承于Adapter。ListAdapter是一個ListView和list上的資料之間的橋梁。資料經常來自于一個Cursor,但這不是必須的。ListView能顯示任何資料,隻要它是被一個ListAdapter包裝的。
BaseAdapter抽象類:是一個實作了既能在ListView(實作了ListAdapter接口)和Spinner(實作了Spinner 接口)裡用的Adapter類的一般基類。
ArrayAdapter類:一個管理這樣的ListView的ListAdapter:這個ListView被一個數組所支援。這個數組可裝任意對象。預設狀态下,這個類預期能這樣:提供的資源id與一個單獨的TextView相關聯。如果你想用一個更複雜的layout,就要用包含了域id的構造函數。這個域id能夠與一個在更大的layout資源裡的TextView相關聯。它将被在數組裡的每個對象的toString()方法所填滿。你可以添加通常對象的lists或arrays。重寫你對象的toString()方法來決定list裡哪一個寫有資料的text将被顯示。如果想用一些其它的不同于TextView的view來顯示數組(比如ImageViews),或想有一些除了toString()傳回值所填在views裡的以外的資料,你就要重寫getView(int,View,ViewGroup)方法來傳回你想要的View類型。
SimpleAdapter類:一個使靜态資料和在XML中定義的Views對應起來的簡單adapter。你可以把list上的資料指定為一個 Map範型的ArrayList。ArrayList裡的每一個條目對應于list裡的一行。Maps包含着每一行的資料。你先要指定一個XML,這個 XML定義了用于顯示一行的view。你還要指定一個對應關系,這個對應關系是從Map的keys對應到指定的views。綁定資料到views發生在兩個階段:
如果一個simpleAdapter.ViewBinder是可用的,那麼 SetViewValue(android.view.View,Object,String)要被調用。如果傳回true,那麼綁定發生了。如果傳回 false,那麼如下views将被按順序地嘗試:
~實作了Checkable的View(如CheckBox),預期的綁定值是boolen
~TextView,預期的綁定值是String,并且SetViewText方法被調用
~ImageView,預期的綁定值是一個資源的id或String。并且SetViewImage方法被調用
如果沒有合适的綁定被發現,一個IllegalStateException被抛出。
下面是一個SimpleAdapter的例子:
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/MyListView">
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget0"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
android:id="@+id/ItemImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:id="@+id/ItemTitle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:layout_x="50px"
android:textSize="15pt">
android:id="@+id/ItemText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:layout_x="50px"
android:layout_y="40px">
android:id="@+id/ItemCheck"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="270px">
public class TestUIList extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ListView list = (ListView) findViewById(R.id.MyListView);
ArrayList> lstImageItem = new ArrayList>();
String[] str1 = { "row one", "row two", "row three", "row four" };
String[] str2 = { "第一行", "第二行", "第三行", "第四行" };
for (int i = 0; i < str1.length; i++) {
HashMap map = new HashMap();
map.put("ItemImage", R.drawable.icon);
map.put("ItemTitle", str1[i]);
map.put("ItemText", str2[i]);
lstImageItem.add(map);
}
SimpleAdapter saImageItems = new SimpleAdapter(this,
lstImageItem,
R.layout.simple_list_item_2,
new String[] { "ItemImage", "ItemTitle", "ItemText" },
new int[] { R.id.ItemImage, R.id.ItemTitle, R.id.ItemText });
list.setAdapter(saImageItems);
}
}
如果你的ListView的每一行想實作被點選後有響應事件。最省事發的方法是繼承一個ListActivity。ListActivity是一個這樣的Activity:這個Activity能通過綁定到一個像array或cursor這樣的資料源來顯示一些items的list,并且當使用者選了一個item時,能夠暴露事件句柄。
ListActivity擁有一個ListView對象。這個ListView對象能夠被綁定到不同的資料源,特别是一個數組或者一個擁有查詢結果的Cursor。ListActivity有三種用法,分别是Binding,Screen Layout和Row Layout。下面僅讨論一下Screen Layout:
ListActivity有一個預設的layout。這個 layout是由一個在螢幕中央的、單獨的、全屏的list構成。然而,如果你想的話,你可以通過在onCreate()裡調用 setContentView()方法來設定你自己的view layout的方式制定螢幕layout。要這樣做,你自己的view必須包含一個id為“@android:id/android:list”(或者在代碼中有list對象)。
随意地,當你制定這個view是空的時,你能夠包含任何類型的view對象來顯示。這個“空list”通知者必須有一個 id“android:empty”。
注意,最後一定要調用setListAdapter(adapter)方法來把通過Adapter綁定了資料的這個List顯示出來。 setListAdapter方法間接調用了Adapter的getView方法,其作用是傳回你想要的view類型。而且當點選listView裡的 item時,會根據getView重畫這個ListView。例子可參見《Android SDK開發大全》中的“資料總管“的例子。
想要實作事件監聽,就要重寫 protected void onListItemClick(ListView l, View v, int position,long id)方法。
想要把在XML中自定義了一行的view逐行顯示在ListActivity中自定義的ListView中,并且在每行動态綁定資料的話,一般要自己寫一個MyAdapter類,這個Adapter繼承BaseAdapter并且其構造函數中至少有一個List參數來實作動态綁定資料。有兩個重要的步驟:
1)重寫getView方法,其中一重要步驟就是用items.get(position)方法來獲得被傳入的資料。其中items是一個 List,它被賦了傳入的List參數的值。position是這個資料在ListView中的行數。Get傳回的是E類型.即List中的模闆類型。
2)寫一個内部類private class ViewHolder。這個内部類隻有成員變量,它們就是你想在ListView中的一行裡要顯示的小View成分。
要想在Adapter中動态傳入其它類的資料,就要在構造函數中再增加一個(或更多)List參數。
最後我們給出自己些的MyAdapter配合ListActivity實作監聽事件的例子:
package com.li.android.myhome;
import java.util.ArrayList;
import java.util.List;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
public class MyHome extends ListActivity
{
private List items = null;
protected void onCreate(Bundle icicle)
{
super.onCreate(icicle);
setContentView(R.layout.main);
items = new ArrayList();
String[] titles =
{ "預設主題", "主題-A", "主題-B", "中秋佳節", "粉紅女郎", "花樣年華" };
for (int i = 0; i < titles.length; i++)
{
String title = titles[i];
items.add(title);
}
setListAdapter(new MyAdapter(this, items));
}
@Override
protected void onListItemClick(ListView l, View v, int position,
long id)
{
new AlertDialog.Builder(MyHome.this).setItems(
R.array.items_my_dialog, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichcountry)
{
}
})
.show();
}
}
package com.li.android.myhome;
import java.util.List;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class MyAdapter extends BaseAdapter
{
private LayoutInflater mInflater;
private Bitmap icom_theme;
private Bitmap icon_selected32;
private List items;
public MyAdapter(Context context, List it)
{
mInflater = LayoutInflater.from(context);
items = it;
icom_theme = BitmapFactory.decodeResource(context.getResources(),
R.drawable.theme);
icon_selected32 = BitmapFactory.decodeResource(context
.getResources(), R.drawable.selected32);
}
public int getCount()
{
return items.size();
}
public Object getItem(int position)
{
return items.get(position);
}
public long getItemId(int position)
{
return position;
}
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder;
convertView = mInflater.inflate(R.layout.file_row, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.mTheme = (ImageView) convertView.findViewById(R.id.theme);
holder.mSelected32 = (ImageView) convertView
.findViewById(R.id.selected32);
holder.mTheme.setImageBitmap(icom_theme);
String title = items.get(position);
holder.text.setText(title);
holder.mSelected32.setImageBitmap(icon_selected32);
return convertView;
}
private class ViewHolder
{
TextView text;
ImageView mTheme;
ImageView mSelected32;
}
}
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget0"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_x="50px"
android:textSize="15pt">
android:id="@+id/selected32"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="270px">