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项目的创建日期。
- 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。
- 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来显示联系人信息:
- 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的例子:
/*--------------------mail.xml---------------------------*/
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">
/*--------------------simple_list_item_2.xml---------------------------*/
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">
/*--------------------TestUIList.java---------------------------*/
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实现监听事件的例子:
/*--------------------MyHome.java-----------------------*/
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();
}
}
/*--------------------MyAdapter.java-----------------------*/
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;
}
}
/*--------------------main.xml-----------------------*/
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"
>
/*-------------------- file_row.xml-----------------------*/
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">