一、前言
Android是完全遵循MVC模式设计的框架,Activity
是Controller
,layout
是View
。因为layout
五花八门,很多数据都不能直接绑定上去,所以Android引入了Adapter
这个机制作为复杂数据展示的转换载体,所以各种Adapter
只不过是转换的方式和能力不一样而已。
Adapter
是将数据绑定到UI界面上的桥接类。Adapter
负责创建显示每个项目的子View
和提供对下层数据的访问。
在多数情况下,你不需要创建自己的Adapter
。Android
提供了一系列Adapter
来将数据绑定到UI Widget
上。
因为Android
负责提供数据和选择用于显示每个项目的View
,所以Adapter
能快速地修改要绑定的控件的外观和功能。
Adapter
是用来帮助填充数据的中间桥梁,比如通过它将数据填充到ListView, GridView, Gallery
。
而android 提供了以下几种:Adapter:ArrayAdapter,BaseAdapter,CursorAdapter, HeaderViewListAdapter,ListAdapter,ResourceCursorAdapter,SimpleAdapter, SimpleCursorAdapter, SpinnerAdapter, WrapperListAdapter
。
根据数据来源形式的不同可以选择不同的Adapter
,比如数据来源于一个Arraylist
就使用BaseAdapter,SimpleAdapter,而数据来源于通过查询数据库获得Cursor那就使用。
几种常用的Adapter
:
二、Adapter 体系介绍
首先来看一下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类型。
- SimpleCursorAdapter类:SimpleCursorAdapter绑定View到Content Provider查询返回的游标上。指定一个XML layout定义,然后将数据集中的每一列的值绑定到layout中的一个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
被抛出。
不论是那种适配器模式,也不管是Listview
也好还是gridview也好,对他们填充数据,都是分三步走。
第一:创建一个数据填充的对象,可以是ListView, GridView, Gallery。
listView=(ListView) findViewById(R.id.listview_simple);
第二步:创建一个数据填充器,可以是BaseAdapter、SimpleAdapter,也可以是与数据库相关联的CursorAdapter
。
例如:SimpleAdapter
可以使用系统封装好的,你也可以自己去继承一个Simpleadapter,来重写其中的方法。继承 simpleadapter
的好处在于,你可以对listitem中每个单一的控件设置监听事件等等一系列操作。如果用的是系统封装好的就有点爱莫能助了。
直接使用系统封装的:
SimpleAdapter simpleAdapter = new SimpleAdapter(this,data, R.layout.simple_item, new String[] { "name", "info" }, new int[] { R.id.simple_name, R.id.simple_info });
重写系统的simpleadpter
:
public class ListSimpleAdpter extends SimpleAdapter{ //要使用到的数据源 private List<MAP><STRING,object="">> data=new ArrayList<MAP><STRING, object="">>(); //填充数据的资源文件 private int resource; private String[] from; private Context context; public ListSimpleAdpter(Context context, List<MAP><STRING, object="">> data, int resource, String[] from, int[] to) { super(context, data, resource, from, to); this.context=context; this.data=data; this.resource=resource; this.from=from; } //item的总行数 @Override public int getCount() { return data==null?0:data.size(); } //item对象 @Override public Object getItem(int position) { return position; } //item的id @Override public long getItemId(int position) { return position; } //绘制每一个item @Override public View getView(int position, View convertView, ViewGroup parent) { Holder holder = null; if(convertView==null) { convertView=LayoutInflater.from(context).inflate(resource, null); holder=new Holder(); holder.imageView=(ImageView) convertView.findViewById(R.id.listitem_pic); holder.title=(TextView) convertView.findViewById(R.id.listitem_title); holder.content=(TextView) convertView.findViewById(R.id.listitem_content); convertView.setTag(holder); } else { holder=(Holder) convertView.getTag(); } holder.imageView.setImageResource(Integer.parseInt(data.get(position).get(from[0]).toString())); holder.title.setText(data.get(position).get(from[1]).toString()); holder.content.setText(data.get(position).get(from[2]).toString()); return convertView; } class Holder{ ImageView imageView; TextView title; TextView content; } }
第三步:将数据填充到对象中去
listView.setAdapter(simpleAdapter);
这样就完成了数据填充器的数据填充。
下面是一个SimpleAdapter
的例子:
/*--------------------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
。想要实现事件监听,就要重写 protected void onListItemClick(ListView l, View v, int position,long id)
方法。
想要把在XML中自定义了一行的view逐行显示在ListActivity
中自定义的ListView中,并且在每行动态绑定数据的话,一般要自己写一个MyAdapter
类,这个Adapter继承BaseAdapter
并且其构造函数中至少有一个List参数来实现动态绑定数据。有两个重要的步骤:
- 重写getView方法,其中一重要步骤就是用items.get(position)方法来获得被传入的数据。其中items是一个List,它被赋了传入的List参数的值。position是这个数据在ListView中的行数。Get返回的是E类型.即List中的模板类型。
- 写一个内部类
private class ViewHolder
。这个内部类只有成员变量,它们就是你想在ListView
中的一行里要显示的小View成分。
三、Adapter 应用实战
要想在Adapter
中动态传入其它类的数据,就要在构造函数中再增加一个(或更多)List参数。
最后给出自己写的MyAdapter配合ListActivity实现监听事件的例子:
/*--------------------MyHome.java-----------------------*/ 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-----------------------*/ 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; } }
四、Adapter 应用总结
Adapter
在Android中占据一个重要的角色,它是数据和UI(View)
之间一个重要的纽带。在常见的View(ListView,GridView)
等地方都需要用到Adapter
。下图直观的表达了Data、Adapter、View
三者的关系。