Adapter是数据和显示数据的View子类的之间的桥梁,一些常见的View子类,例如ListView、GridView之类的,都需要使用Adapter来将数据和显示界面关联起来。这里所说的数据是指数组、集合、链表、数据库之类的。还有自动完成文本框也要使用Adapter。
android中的Adapter类的继承关系:(蓝色的是接口,绿色的是类)
其中BaseAdapter、CursorAdapter、ResourceCursorAdapter是抽象类,没法直接实例化一个对象,能够直接实例化对象的且比较常用的就是ArrayAdapter、SimpleAdapter和SimpleCursorAdapter
先来看看所有适配器的老爸Adapter中有哪些方法:
getCount():返回适配器将要呈现的数据的数目;
getItem(int position):返回数据集合中指定位置的数据;
getItemId(int position):返回数据集合中指定位置处的数据的id;
hasStableIds():表明数据中的id是否是稳定的,因为经过Adapter适配的数据可以删除或插入,所以会导致数据的id为改变,如果数据集合中的每一个数据和他的id都是对应不变的,就返回true;
getView(int position, View convertView, ViewGroup parent):返回与指定位置处数据相关联的View对象,可以是从xml文件中解析出来的,也可以是自定义的;
getItemViewType(int position):返回一个表明视图类型的整形数据。
下面通过一个简单的例子分别来看这几个方法的作用,由于Adapter是一个接口,没法实例化一个对象,所以下面的代码中我使用ArrayAdapter代替Adapter,来看看Adapter接口中的方法的作用。
代码1:
import android.app.Activity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class Test1 extends Activity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test1);
// 用于显示数据的View子类对象ListView
listView = (ListView) findViewById(R.id.listView1);
// 将要显示的数据,在这里我定义为一个字符串数组
String[] str = new String[] { "工商银行", "农业银行", "建设银行", "中国银行" };
ArrayAdapter adapter = new ArrayAdapter(this,
android.R.layout.simple_expandable_list_item_1, str);
listView.setAdapter(adapter);
System.out.println("适配器中的数据的数目为:"+adapter.getCount());
System.out.println("id为2的数据为:"+adapter.getItem(2));//id是从0开始计算的
System.out.println("序号为2的数据的id为:"+adapter.getItemId(2));
System.out.println("适配器中的数据的id是否稳定:"+adapter.hasStableIds());
System.out.println("序号为2的数据的相关的View对象为:"+adapter.getView(2, null, listView));
System.out.println("序号为2的数据的相关的View对象的类型为:"+adapter.getItemViewType(2));
}
}
布局文件中只有一个ListView组件,就不放出来了运行结果:
最后一个方法一直没有明白是什么意思,如果哪位大哥知道,还请指点。
下面看几个常用的适配器。
一、ArrayAdapter,支持泛型操作,它还有一些其他的功能:各种构造函数、插入或删除数据、给数据排序、过滤数据(只让指定的数据能够显示出来)
在实例化一个适配器对象的时候就指定了适配器适配的数据集合,来看构造函数:
1、ArrayAdapter(Context context, int resource)
这个构造函数很少用,因为没有指定要显示的数据。说明一下第二个参数resource:API上说的是:包含了一个TextView组件的xml文件的id。它起到一个将数据映射到ListView(或者GridView之类的)上的作用。这个xml文件中只能有这一个TextView组件,且不能有父组件,一般使用系统中定义好的xml文件,在SDK的安装文件下的platforms\android-7(或android-1.5,当然如果你下载了其他版本系统,里面也有的)\data\res\layout中,里面经常使用的两个是android.R.layout.simple_list_item_1和android.R.layout.simple_list_item_2,前者里面有一个TextView组件,后者有两个;当然,如果你觉得系统提供的xml文件不好看,那你也可以使用自己定义的xml文件,自定义的xml文件同样要满足前面说的两点要求。说明一下,系统并没有给前面说的两个布局文件中的TextView组件的text属性赋值。
2、ArrayAdapter(Context context, int resource, int textViewResourceId)
这个构造函数也没有指定要显示的对象,第二个参数表示一个布局文件的id(当然,跟前面一样可以使用系统自带的,也可以使用自定义的)这个布局文件中也可以包含其他组件,就跟普通的布局文件一样。第三个参数是这个布局文件中的一个TextView组件,这个TextView组件规定了ListView中每一项中的字符串对象(在只显示字符串的情况下,可以理解为TextView)的形式。
3、ArrayAdapter(Context context, int resource, T[] objects)
这个构造函数指定要显示的数据是一个数组,具体是什么类型的数组,那就有由户自己决定了,例如代码1中使用的是一个字符串数组;前两个参数跟第二个构造函数的参数一样。
4、ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects)
这个构造函数就是第二个和第三个构造函数的结合,前面说了的,在这也没什么好说的。
5、ArrayAdapter(Context context, int resoutce, int textViewResourceId, List
这个构造函数就是把第四个构造函数中要显示的数据又一个字符串数组换成了一个集合,其他的意思也是一样的。
看一个例子(没有使用系统提供的布局文件,使用的是自定义的布局文件)
代码2:
import android.app.Activity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
public class Test1 extends Activity {
private ListView listView;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test1);
//textView = (TextView)findViewById(R.id.textView1);
// 用于显示数据的View子类对象ListView
listView = (ListView) findViewById(R.id.listView1);
// 将要显示的数据,在这里我定义为一个字符串数组
String[] str = new String[] { "工商银行", "农业银行", "建设银行", "中国银行" };
ArrayAdapter adapter = new ArrayAdapter(this, R.layout.temp,R.id.textView5,str);
listView.setAdapter(adapter);
}
}
布局文件temp.xml(还有一个布局文件activity_test1.xml里面就只有一个ListView组件,就不放出来了)
运行结果:
我们发现字符串数组显示在Button上去了,如果我把布局文件中的ImageView拿到Button的前面去,运行的结果就是这样
字符串自然没法显示在ImageView上,但是也没有显示在第二个Button上,而是显示在最后一个TextView上了,这是一个比较奇怪的现象,我还没有摸索出什么规律来,多的不说了。
总结一下上面的构造函数所有的参数,第一个参数没必要说;int resource:规定ListView(或者其他的类似的组件)的每一项的布局,就跟普通的布局文件一样,可以包含TextView、Button、ImageView之类的各种组件;int textViewResourceId,如果ListView显示的是字符串(有点像TextView),则它用于规定每一个字符串对象的形式(字体颜色、大小之类的);其他的参数就都是要显示出来的数据(数组、集合、链表、数据库查询结果之类的)。
对于上面的两段程序,大家可能也发现了,除了我指定的一个字符串数组以外,ListView的每一项对应的布局文件中的其他组件都是一样的(就像上面的那张图一样,除了几个银行名字之外,图片和按钮都是一样的),很多时候ListView的每一项显示的内容并不是一样的,我们可以在代码中获取这些组件的引用(findViewById),然后重新给他赋值就行了。如果ListView有很多项,我每一项都这样去重新赋值的话,肯定不方便,那就需要使用另外的一个适配器了SimpleAdapter。
二、SimpleAdapter,这个适配器只有一个构造函数,但是它能够显示的内容还是很丰富的,它经常会跟Map对象一起使用。下面看一下他的构造函数
SimpleAdapter(Context context, List extends Map
说明一下他的几个参数
1、Context context没什么好说的;
2、List extends Map
3、int resource 跟ArrayAdapter的构造函数中的一样,这是一个布局文件的id,它定义了ListView的每一项的布局;
4、String[] from 用来表示ListView的每一项中要显示的内容的字符串(也就是Map对象中的“key”);
5、int[] to 这个是每一项中要显示的View对象的id的数组(也就是Map对象中的“value”)。
需要说明一下的是,第二个参数(也就是那个Map对象),它必须包含第四个参数(字符串数组)中定义的所有的项;第三个参数(布局文件)必须包含第五个参数(View对象的id),当然你也可以包含其他的组件。不过跟上面一样,显示出来的结果是,这个“其他的组件”在ListView的每一项中都是一样的。
看一段代码
代码3:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import android.widget.SimpleAdapter;
public class Test2 extends Activity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test2);
listView = (ListView)findViewById(R.id.listView1);
List
布局文件temp2.xml(另外一个布局文件activity_test2.xml里面只有一个ListView,就不放出来了)
运行结果
说明一下,
1、上面的图片虽然还是一样的,那是因为我在实例化Map对象的时候,往里面放的图片都是一样的,实际情况下可以根据自己的需要放不同的图片,而且我在ListView的每一项中只放了两个组件,你也可以放很多其他的组件;
2、上面的代码中ListView的每一项显示的都是一个ImageView和一个TextView,当然,如果需要的话,你也可以多显示点东西,那就在布局文件temp2.xml中添加就行了,每一项的布局的风格也都是在temp2.xml中设置。
Adapter还有一个比较强悍的功能,那就是过滤,查看API我们会发现,
三、SimpleCursorAdapter,这个适配器在使用数据库的时候用的比较多。我们知道从数据库中查询的结果一般是一个Cursor对象,看这个适配器的名字就知道,他肯定跟数据库有点关系了。这个适配器跟其他的适配器最大的不同在于它能够跟数据绑定。正式使用这个适配器之前,先说一下,很容易犯的一个错误,先看一下是什么错误
说'_id'不存在,因为SimpleCursorAdapter只认识'_id',所以数据表的主键必须命名为'_id',不知道为什么会有这个规定,但是这么做的话貌似的确可以解决问题。
先看一下它的构造函数
1、SimpleCursorAdapter (Context context, int layout, Cursor c, Stirng[] from, int[] to) 这个构造函数跟SimpleAdapter的构造函数相比只是把List对象换成了Cursor对象,其他的参数的意思都是差不多的,说明一下,这个构造函数已经不推荐使用了。
2、SimpleCursorAdapter (Context context, int layout, Cursor c, String[] from, int[] to, int flags) 最后一个参数flags,用于定义适配器的一些行为,CursorAdapter提供了为这个参数提供了两个值:FLAG_AUTO_REQUARY,要求Cursor查询在主线程中进行,这就意味着有可能出现ANR,所以一般不进行这个设置;FALG_REGISTER_CONTENT_OBSERVER,如果为适配器设置了这个,就会在Cursor对象中注册一个数据观察者,具体起啥作用,看下面的代码吧。
代码4:
package com.example.adapter;
import android.app.Activity;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.widget.SimpleCursorAdapter;
import android.widget.ListView;
public class Test3 extends Activity {
private ListView listView;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test3);
listView = (ListView) findViewById(R.id.listView1);
DBquery dbquery = new DBquery(this);
Cursor cursor = dbquery.query("select * from bank", null);
@SuppressWarnings("deprecation")
SimpleCursorAdapter simpleCursorAdapter = new SimpleCursorAdapter(this,
R.layout.temp3, cursor,
new String[] { "name"}, new int[] { R.id.textView3},CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
listView.setAdapter(simpleCursorAdapter);
}
}
数据库类:
package com.example.adapter;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DBquery extends SQLiteOpenHelper {
// 构造函数
public DBquery(Context context) {
super(context, "test.db", null, 1);
// TODO Auto-generated constructor stub
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
String sql = "create table [bank](" + "[_id] autoinc,"
+ "[name] varchar(20) not null on conflict fail,"
+ "constraint [sqlite_autoindex_bank_1] primary key ([_id]))";
db.execSQL(sql);
String[] sql_init = new String[] { "insert into bank values(1,'农业银行')",
"insert into bank values(2,'建设银行')",
"insert into bank values(3,'工商银行')",
"insert into bank values(4,'中国银行')" };
for (String order : sql_init) {// 执行每条SQL语句
db.execSQL(order);
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method
}
public Cursor query(String sql, String[] args) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery(sql, args);
return cursor;
}
}
布局文件temp3.xml:
运行结果:
上面的一个ImageView是我在布局文件中定义的,所有的都是一样的,因为SimpleCursorAdapter操作的对象是Cursor对象,所以不能像在SimpleAdapter中一样任意指定ImageView显示的图片。
四、上面使用的都是系统提供的适配器,我们也可以根据自己的需要定义自己的适配器,只需要继承适配器类或者实现适配器接口就行了。原理跟使用系统提供的适配器是一样的。下面看一个继承BaseAdapter的例子。
代码5:
package com.example.adapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
public class MainActivity extends Activity {
private ListView listView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test4);
listView = (ListView) findViewById(R.id.listView4);
List> list = new ArrayList>();
Map map = new HashMap();
map.put("title", "工商银行");
map.put("img", R.drawable.ic_launcher);
list.add(map);
map = new HashMap();
map.put("title", "农业银行");
map.put("img", R.drawable.ic_launcher);
list.add(map);
map = new HashMap();
map.put("title", "建设银行");
map.put("img", R.drawable.ic_launcher);
list.add(map);
map = new HashMap();
map.put("title", "中国银行");
map.put("img", R.drawable.ic_launcher);
list.add(map);
Test4 adapter = new Test4(this, list);
listView.setAdapter(adapter);
}
}
适配器类:
package com.example.adapter;
import java.util.List;
import java.util.Map;
import android.content.Context;
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 Test4 extends BaseAdapter {
private Context context;
private LayoutInflater layoutInflater;
private List> list;
// 构造函数
public Test4(Context context, List> list) {
this.context = context;
layoutInflater = LayoutInflater.from(context);
this.list = list;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return this.list != null ? this.list.size() : 0;
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return this.list.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
if (convertView == null) {
//这里我是从xml文件中解析出来的一个View对象,也可以使用自己定义的
convertView = layoutInflater.inflate(R.layout.temp4, null);
}
// 得到条目中的子组件
ImageView iv = (ImageView) convertView.findViewById(R.id.imageView4);
TextView tv = (TextView) convertView.findViewById(R.id.textView4);
iv.setImageResource((Integer) list.get(position).get("img"));
tv.setText(list.get(position).get("title").toString());
return convertView;
}
}
布局文件temp4.xml跟代码3的temp3.xml是一样的,activity_test4.xml中只有一个ListView。
运行结果,跟代码3的运行结果是一样的。
我们发现,使用自定义的适配器和系统提供的适配器最大的区别就在于构造函数上,自定义的适配器我们可以根据需要定义不同的构造函数,因为适配的数据是在实例化适配器对象的时候指定的,这也就意味着自定义的适配器我们可以随意的指定适配的数据对象。
查看API我们知道,ArrayAdapter、CursorAdapter、SimpleAdapter都实现了Filterable接口,说明这些适配器都可以对适配的数据进行过滤,看一个例子
代码6:(把代码3改成下面这样,然后删除temp2.xml文件中的ImageView)
package com.example.adapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Filter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
public class Test2 extends Activity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test2);
listView = (ListView)findViewById(R.id.listView7);
List> list = new ArrayList>();
//List里面放的是Map对象,所以下面声明几个Map对象
Map map = new HashMap();
map.put("title", "工商银行");
//map.put("img", R.drawable.ic_launcher);
list.add(map);
map = new HashMap();
map.put("title", "农业银行");
//map.put("img", R.drawable.ic_launcher);
list.add(map);
map = new HashMap();
map.put("title", "建设银行");
//map.put("img", R.drawable.ic_launcher);
list.add(map);
map = new HashMap();
map.put("title", "中国银行");
//map.put("img", R.drawable.ic_launcher);
list.add(map);
String[] from = new String[]{"title"/*, "img"*/};
int[] to = new int[]{R.id.textView1/*, R.id.imageView1*/};
SimpleAdapter adapter = new SimpleAdapter(this, list, R.layout.temp2, from, to);
String str = new String("中国银行");//设置过滤的约束条件
CharSequence constraint = str.subSequence(0, str.length());
Filter filter = adapter.getFilter(); //得到一个过滤器
filter.filter(constraint); //为该过滤器设置约束条件
listView.setAdapter(adapter);
}
}
设置过滤的条件也就是能够显示出来的数据。其他的适配器的过滤作用的实现也是一样的。