概念
Adapter是连接后端数据和前端显示的适配器接口,是数据和UI(View)之间一个重要的纽带,在常见的View(ListView,GridView)等地方都需要用到Adapter。Adapter对后端数据进行处理,并告知相应的View应该怎样布局和运用这些数据。整个过程需要一下三个要素,缺一不可:
1、View 用来展示列表的View,例如本文用的ListView。
2、Adapter 用来把数据映射到ListView上的中介。
3、Data 具体的将被映射的字符串,图片,或其它基本组件。
如下图直观的表达了Data、Adapter、View三者的关系:
android.widget.Adapter中所实现的接口和类一览:
由图可以看到在Android中与Adapter有关的所有接口、类的完整层级图。在我们使用过程中可以根据自己的需求实现接口或者继承类进行一定的扩展。比较常用的有 BaseAdapter,SimpleAdapter,ArrayAdapter,SimpleCursorAdapter等。
- BaseAdapter是一个抽象类,继承它需要实现较多的方法,所以也就具有较高的灵活性;
- ArrayAdapter支持泛型操作,最为简单,只能展示一行字。
- SimpleAdapter有最好的扩充性,可以自定义出各种效果。
- SimpleCursorAdapter可以适用于简单的纯文字型ListView,它需要Cursor的字段和UI的id对应起来。如需要实现更复杂的UI也可以重写其他方法。可以认为是SimpleAdapter对数据库的简单结合,可以方便地把数据库的内容以列表的形式展示出来。
ArrayAdapter
示例代码:
lv1 = (ListView)findViewById(R.id.lv1);
List<String> data = new ArrayList<String>();
data.add("测试数据1");
data.add("测试数据2");
data.add("测试数据3");
data.add("测试数据4");
lv1.setAdapter(new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_expandable_list_item_1,data));
上面代码使用了ArrayAdapter(Context context, int textViewResourceId, List<T> objects)来装配数据,ArrayAdapter的构造需要三个参数,依次为运行环境(俗称上下文,通常是当前Activity.this)、布局文件(注意这里的布局文件描述的是列表的每一行的布局,android.R.layout.simple_list_item_1是系统定义好的布局文件只显示一行文字)和数据源(一个List集合)。最后用setAdapter()为Listview加载该适配器。
由于ArrayAdapter的第三个参数支持泛型,因此也可使用数组来作为数据源,示例:
String[] str = {"测试数据1","测试数据2","测试数据3","测试数据4"};
lv1.setAdapter(new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_expandable_list_item_1,str));
两段代码所运行的效果均如下图:
SimpleAdapter
相对ArrayAdapter而言,SimpleAdapter的扩展性要更好一些,主要体现在UI显示上。SimpleAdapter可以定义各种各样的布局出来,可以放上ImageView,还可以放上Button、CheckBox等等(实际上Button和CheckBox放上去实用性不大,因为无法在此设置监听)。总的来说,SimpleAdapter能够创造更好的布局,但布局里面的空间不能单独与用户进行交互。看示例:
vlist.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" >
<ImageView
android:id="@+id/listiv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/listtv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/listtv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
MainActivity.java:
package com.plusjun.hello23;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.os.Bundle;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.app.Activity;
public class MainActivity extends Activity {
ListView lv1;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv1 = (ListView)findViewById(R.id.lv1);
lv1.setAdapter(new SimpleAdapter(MainActivity.this, getData(),R.layout.vlist,new String[]{"title","info","img"}, new int[]{R.id.listtv1,R.id.listtv2,R.id.listiv1}));
}
private List<Map<String, Object>> getData() {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
Map<String, Object> map = new HashMap<String, Object>();
map.put("title", "G1");
map.put("info", "google 1");
map.put("img", R.drawable.img1);
list.add(map);
map = new HashMap<String, Object>();
map.put("title", "G2");
map.put("info", "google 2");
map.put("img", R.drawable.img2);
list.add(map);
map = new HashMap<String, Object>();
map.put("title", "G3");
map.put("info", "google 3");
map.put("img", R.drawable.img3);
list.add(map);
return list;
}
}
以上示例中在res.drawable文件夹中已放入img1.png、img2.png和img3.png。
使用simpleAdapter的数据用一般都是HashMap构成的List,list的每一节对应ListView的每一行。HashMap的每个键值数据映射到布局文件中对应id的组件上,通常我们是自己定义一个布局vlist.xml。SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to):
第一个参数:运行环境(俗称上下文,通常是当前Activity.this);
第二个参数:数据源;
第三个参数:要使用的布局文件;
第四个参数:该参数为对应数据源的键,一次投射到第五个参数中的控件中;
第五个参数:布局文件(第三个参数)所对应的控件,依次接受第四个参数的投射。
通俗的说,就是把第四个参数所声明的键所对应的值投射到第五个参数所对应的控件上。
运行效果如下图:
SimpleCursorAdapter
SimpleCursorAdapter可使用自定义布局文件,但布局文件中只能有TextView。注意,一定要以Cursor作为数据源的时候才能使用SimpleCursorAdapter
lv1.setAdapter(new SimpleCursorAdapter(this,android.R.layout.simple_expandable_list_item_2, cursor,new String[]{"name","tel" }, new int[]{android.R.id.text1,android.R.id.text2}));
在这里lv1为ListView对象,cursor假设为已经取得数据的Cursor对象。
构造方法与simpleAdapter相似,但有些改变:第一个参数:运行环境(俗称上下文,通常是当前Activity.this);
第二个参数:要使用的布局文件;
第三个参数:数据源,只能是Cursor对象
第四个参数:该参数为对应数据源的键,一次投射到第五个参数中的控件中;
第五个参数:布局文件(第三个参数)所对应的控件,依次接受第四个参数的投射。
BaseAdapter
这是本文最重要的地方,也是最难以理解的部分。上文提到的各种适配器可以创造出各式各样的UI,但你是否发现,如果采用这些系统自带的适配器,对于事件的响应只能局限在一个行单位。有时候,列表不光会用来做显示用,我们同样可以在在上面添加按钮。添加按钮首先要写一个有按钮的xml文件,然后自然会想到用上面的方法定义一个适配器,然后将数据映射到布局文件上。但是事实并非这样,因为按钮是无法映射的,即使你成功的用布局文件显示出了按钮也无法添加按钮的响应。这时就要继承并重写BaseAdapter。下面的示例将显示一张图片,两行文字和一个按钮,点间你就会出现对话框,我们来看看如何实现。
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<ListView
android:id="@+id/lv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
vlist2.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" >
<ImageView
android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/info"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<Button
android:id="@+id/view_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:text="Button" />
</LinearLayout>
MainActivity.java
package com.plusjun.hewlloxml;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends Activity {
private ListView lv1;
private List<Map<String, Object>> mData;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv1 = (ListView)findViewById(R.id.lv1);
mData = getData();
lv1.setAdapter(new MyAdapter(MainActivity.this, mData));
}
private List<Map<String, Object>> getData() {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
//每个HashMap所携带的数据为一行ListView的数据,然后集成到一个ArrayList链表中
//有多少个HashMap,就有多少行ListView数据
Map<String, Object> map = new HashMap<String, Object>();
map.put("title", "G1");
map.put("info", "google 1");
map.put("img", R.drawable.i1);
list.add(map);
map = new HashMap<String, Object>();
map.put("title", "G2");
map.put("info", "google 2");
map.put("img", R.drawable.i2);
list.add(map);
map = new HashMap<String, Object>();
map.put("title", "G3");
map.put("info", "google 3");
map.put("img", R.drawable.i3);
list.add(map);
return list;
}
//实现按钮点击所调用的函数,效果为显示一个AlertDialog
public void showInfo(){
new AlertDialog.Builder(this)
.setTitle("我的listview")
.setMessage("介绍...")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
}
}).show();
}
//重写BaseAdapter,这样才可以手动映射数据
public class MyAdapter extends BaseAdapter{
//LayoutInflater类用于将布局文件(.xml)实例化
private LayoutInflater mInflater;
private List<Map<String, Object>> mData;
//自创的构造方法
public MyAdapter(Context context, List<Map<String, Object>> mData){
//声明该LayoutInflater的运行环境(俗称上下文)(当然不一定要在这里声明)
this.mInflater = LayoutInflater.from(context);
this.mData = mData;
}
@Override
//该方法用于得知将要绘制的行数,也就是数据有多少个节点
//在这里,就是得知List里面有多少个Map
public int getCount() {
// TODO Auto-generated method stub
return mData.size();
}
@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
@Override
//开始重写绘制每一列的方法
public View getView(int position, View convertView, ViewGroup parent) {
HashMap<String, Object> controls = null;
/*
* 只有在第一次调用getView()方法的时候convertView才是空的
* 以后每一次调用getView(),所获得的convertView均是上一次遗留的convertView
* 因此,只需要在第一次使用该方法的时候调用findViewById绑定控件
* 因为以后每一次用的convertView都已经绑定了控件,仅仅需要去需改参数
*/
if (convertView == null) {
controls = new HashMap<String, Object>();
//实例化布局文件
convertView = mInflater.inflate(R.layout.vlist2, null);
ImageView img = null;
TextView title = null;
TextView info = null;
Button viewBtn = null;
img = (ImageView)convertView.findViewById(R.id.img);
title = (TextView)convertView.findViewById(R.id.title);
info = (TextView)convertView.findViewById(R.id.info);
viewBtn = (Button)convertView.findViewById(R.id.view_btn);
//打包到HashMap
controls.put("img", img);
controls.put("title", title);
controls.put("info", info);
controls.put("viewBtn", viewBtn);
//将这些控件参数发送给布局文件
convertView.setTag(controls);
}else {
//将convertView里面的控件参数拿出来,放到一个HashMap里面
controls = (HashMap<String, Object>)convertView.getTag();
}
//HashMap里面的东西逐一取出
ImageView img = (ImageView) controls.get("img");
TextView title = (TextView) controls.get("title");
TextView info = (TextView) controls.get("info");
Button viewBtn = (Button) controls.get("viewBtn");
/*
* 开始修改各项参数信息
* mData是从构造函数里传进来的List
* get(int position)方法指的是取出第position个HashMap
* 得到HashMap后再通过键来获得值
*/
img.setBackgroundResource((Integer)mData.get(position).get("img"));
title.setText((String)mData.get(position).get("title"));
info.setText((String)mData.get(position).get("info"));
viewBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
//这是在前面定义的方法,不是重写。(回过头去看一下前面的代码)
showInfo();
}
});
/*控件参数都修改好了,逐一放入HashMap中
* 当键与HashMap中的键重复时旧数据会被新数据覆盖
*/
controls.put("img", img);
controls.put("title", title);
controls.put("info", info);
controls.put("viewBtn", viewBtn);
/*最后这里的controls就不用convertView.setTag(controls)也能起效
*我也不知道为什么,也没能搞清楚,希望大家能够解答
*/
return convertView;
}
}
}
ListView在开始绘制的时候,系统首先调用getCount()函数,根据他的返回值得到listView的长度,然后根据这个长度,调用getView()逐一绘制每一行。如果你的getCount()返回值是0的话,列表将不绘制。同样,return 1,就只绘制一行。
系统显示列表时,首先实例化一个适配器(这里将实例化自定义的适配器)。适配和映射数据都必须手动,因此这里需要重写getView()方法。系统在绘制列表的每一行的时候将调用此方法。getView()有三个参数,position表示将绘制的是第几行,covertView是从布局文件中inflate来的布局。我们用LayoutInflater的方法将定义好的vlist2.xml文件提取成View实例用来显示。然后将xml文件中的各个组件实例化(简单的findViewById()方法)。这样便可以将数据对应到各个组件上了,至此一个自定义listView的第一行就绘制就完成了。现在让我们回过头从新审视这个过程。系统要绘制ListView了,他首先获得要绘制的这个列表的长度,然后开始绘制第一行,怎么绘制呢?调用getView()函数。在这个函数里面首先获得一个View(实际上是一个ViewGroup),然后再实例并设置各个组件,显示之。好了,绘制完这一行了。那再绘制下一行,直到绘完为止。在实际的运行过程中会发现listView已经失去了以行为单位的焦点了,这是因为Button抢夺了listView的焦点。
效果如下:
以上部分内容转载或参考来源如下:
http://www.cnblogs.com/devinzhang/archive/2012/01/20/2328334.html
http://www.cnblogs.com/topcoderliu/archive/2011/05/07/2039862.html
在此表示感谢。
转载请注明来源,版权归原作者所有,未经同意严禁用于任何商业用途。
微博:http://weibo.com/theworldsong
邮箱:theworldsong@foxmail.com