1.介绍:
Adapter是连接后端数据和前端显示的适配器接口,是数据和UI(View)之间一个重要的纽带。很多常见的View(如:ListView,Spinner,GridView等)都需要用到Adapter。如下图直观的表达了Data、Adapter、View三者的关系:
注意:
(1)适配器用来将不同的数据映射到View上。不同的数据对应不同的适配器,如ArrayAdapter,CursorAdapter, SimpleAdapter等, 他们能够将数组,游标,Map对象列表等数据映射到View上。
(2)正是由于适配器的存在,使得ListView的使用相当灵活,经过适配器的处理后,在 view看来所有的数据映射过来都是一样的。
(3)所有的数据和资源要显示到ListView上都须通过适配器来完成。
(4)系统已有的适配器可以将基本的数据显示到ListView上,但是在实际开发中这些系统已实现的适配器,有时不能满足我们的需求, 要实现复杂的ListView可以通过(1)继承ListView并重写相应的方法来完成, 但更一般的做法是(2)通过继承BaseAdapter来实现。
2.比较常用的Adapter有四类:
(1)ArrayAdapter支持泛型操作,操作最为简单,只能展示一行字符。
(2)SimpleAdapter有很好的可扩充性,可以自定义出各种效果。
(3)SimpleCursorAdapter可以适用于简单的纯文字型ListView,它需要将Cursor的字段和UI显示的id对应起来。可以看作SimpleAdapter和数据库的简单结合,能够方便地把数据库的内容以列表的形式展示出来。
(4)BaseAdapter是一个抽象类,继承它需要实现较多的方法,所以也就具有较高的灵活性。是用于ListView(实现指定的ListAdapter接口)和Spinner(实现指定的SpinnerAdapter接口)的共同实现的一个公共基类适配器。
注意:ArrayAdapter,CursorAdapter, SimpleAdapter都继承于BaseAdapter
3.使用:
(1)写代码定义列表中要显示的底层数据结构,如List
//定义List变量(提供数据)--ArrayList是List的具体实现
List list = new ArrayList();
//添加数据内容
list.add("测试数据--1");
list.add("测试数据--2");
list.add("测试数据--3");
list.add("测试数据--4");
list.add("测试数据--5");
list.add("测试数据--6");
list.add("测试数据--7");
list.add("测试数据--8");
list.add("测试数据--9");
(2)
创建ArrayAdapter对象
参数确定ListView每行的布局和要显示的底层数据
//定义ArrayAdapter
//参数-----上下文环境, ListView的每一行的布局, 底层数据
ArrayAdapter adapter=new ArrayAdapter(this,android.R.layout.simple_list_item_1, list);
使用自定义布局必须给出TextView的ID—可以包含复杂的布局,但要求布局中必须有一个TextView用于显示字符串
//如果要使用自定义的布局,必须指明TextView的ID--布局中也可以包含除TextView之外的其它控件
ArrayAdapter adapter=new ArrayAdapter(this, R.layout.test, R.id._id, list);
(3)调用ListView对象的setAdapter()方法将刚创建的ArrayAdapter对象与ListView对象结合起来。
//设置ListView的Adapter对象
listview.setAdapter(adapter);
(4)需要的话,再利用setOnItemClickListener()为ListView对象添加“点击行”的监听器
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
//parent--ListView
// view--一行
//position--适配器中的序号
// id--row id, ListView中的序号,从0开始编号
//大部分情况下(如:ArrayAdapter 和 SimpleAdapter 中,两者是一样的),position和id相同
//但是,有些情况(如SimpleCursorAdapter中),row id是数据库中的_id字段的值,与position不同
String info=list.get(position) + " was clicked!"; //取出点击的行的内容
Toast.makeText(ArrayAdapterActivity.this, info+"--"+id, Toast.LENGTH_SHORT).show();
}
});
自己写的一个简单模版(布局文件太简单不贴了):
package cn.edu.qtech.csc.lcb.listviewdemo;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class ArrayAdapterActivity extends Activity {
//定义ListView对象变量---View
private ListView listview;
//存放数据的List对象---Model
private List list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_array_adapter);
//获取ListView对象
listview=(ListView)findViewById(R.id.simpleListViewControll);
//定义List变量(提供数据)--ArrayList是List的具体实现
list=new ArrayList();
//添加数据内容
list.add("测试数据--1");
list.add("测试数据--2");
list.add("测试数据--3");
list.add("测试数据--4");
list.add("测试数据--5");
list.add("测试数据--6");
list.add("测试数据--7");
list.add("测试数据--8");
list.add("测试数据--9");
//定义ArrayAdapter,衔接ListView和List---Controller
//参数-----上下文环境, ListView的每一行的布局, List对象
//如果要使用自定义的布局,必须指明TextView的ID--布局中也可以包含除TextView之外的其它控件
//ArrayAdapter adapter=new ArrayAdapter(this, android.R.layout.simple_list_item_1, list);
ArrayAdapter adapter=new ArrayAdapter(this, R.layout.test, R.id._id, list);
//设置ListView的Adapter对象
listview.setAdapter(adapter);
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
//parent--ListView
// view--一行
//position--适配器中的序号
// id--row id, ListView中的序号,从0开始编号
//大部分情况下(如:ArrayAdapter 和 SimpleAdapter 中,两者是一样的),position和id相同
//但是,有些情况(如SimpleCursorAdapter中),row id是数据库中的_id字段的值,与position不同
String info=list.get(position) + " was clicked!"; //取出点击的行的内容
Toast.makeText(ArrayAdapterActivity.this, info+"--"+id, Toast.LENGTH_SHORT).show();
}
});
}
}
(1)通过数据库查询操作得到查询结果 --〉Cursor对象
(2)通过startManagingCursor(cursor)使得Cursor对象的生命周期能够和Activity自动同步
(3)创建SimpleCursorAdapter对象(新版替换为LoaderManager 和 CursorLoader)
参数指定:每行的布局,Cursor对象,Cursor中的列名数组,每行布局中的控件ID--每行布局中多个控件可单独操作
(4)调用ListView对象的setAdapter()方法将刚创建的SimpleCursorAdapter对象与ListView对象结合起来。
(5)需要的话,再利用setOnItemClickListener()为ListView对象添加“点击行”的监听器。
下面的代码我解释一下,我这查的是手机中的联系人,这种高危操作肯定要申请权限了,所以我在配置文件中写了如下代码:
这还不能获得权限(想想APP点击一个按钮后可以获取你这台手机的全部联系人,好可怕啊),还要在经过下面代码前面一部分的权限申请判断过程(不但如此,运行的时候你点击按钮后它还弹出危险提示框,问你是否允许给它这个权限,你点击allow就是允许,这样才能显示)。
package cn.edu.qtech.csc.lcb.listviewdemo;
import android.Manifest;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.widget.Toast;
public class SimpleCursorAdapterActivity extends AppCompatActivity {
//定义ListView对象
private ListView listview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//在当前Activity中动态创建ListView对象
listview=new ListView(this);
//设置Activity的显示界面为ListView--控件ListView充满整个Activity
setContentView(listview);
//判断是否已经取得读联系人的权限
//ContextCompat.checkSelfPermission()检测权限的返回值是PERMISSION_GRANTED和PERMISSION_DENIED
if(ContextCompat.checkSelfPermission(SimpleCursorAdapterActivity.this,
Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(SimpleCursorAdapterActivity.this, //如果没有权限,则动态申请权限
new String[]{Manifest.permission.READ_CONTACTS},1);
}
//获取读"联系人"数据库的cursor
Cursor cursor=getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
//设置Cursor对象的生命周期和Activity自动同步
startManagingCursor(cursor);
//创建Adapter对象--必须指出布局及布局中的TextView的ID与Cursor中的列名的对应关系
//SimpleCursorAdapter已经过时,建议使用 LoaderManager 和 CursorLoader
ListAdapter adapter=new SimpleCursorAdapter(this, //上下文环境
// android.R.layout.simple_expandable_list_item_1, //单行的布局--使用系统定义的布局
R.layout.test, //单行的布局--使用自定义的布局
cursor, //游标对象
new String[]{ContactsContract.Contacts.DISPLAY_NAME},
// new int[]{android.R.id.text1}); //显示用的TextView的ID--系统布局中定义
new int[]{R.id._id}); //显示用的TextView的ID--自定义布局中定义
//设置ListView对象的Adapter
listview.setAdapter(adapter);
//设置点击行的监听器
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
String info;
// info=((TextView)view).getText().toString();//取出当前行的内容显示, 若每行不是一个简单的TextView,则不能如此操作
info=((TextView)((LinearLayout)view).findViewById(R.id._id)).getText().toString(); //自定义View区内容
Toast.makeText(SimpleCursorAdapterActivity.this,
info,
Toast.LENGTH_SHORT).show();
}
});
}
//权限申请结果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//ToDo:已授权
} else {
//ToDo:用户拒绝
Toast.makeText(SimpleCursorAdapterActivity.this,"无权读取联系人信息",Toast.LENGTH_SHORT).show();
}
}
}
看一下我的截图:
我随便加了两个联系人,点击第二个按钮(第一次点击会有弹出权限申请框,之后就没了)后:
(1)定义并填充存储底层数据的数据结构,如:List Map集合类用于存储元素对(称作“键”和“值”),其中每个键映射到一个值—适合于用来存储复杂数据。
(2)创建SimpleAdapter对象
参数:每行的布局,List对象,Map对象中的的键名数组,每行布局中的控件ID
(3)设置Adapter
(4)需要的话,可以添加点击行监听器
废话不多直接上代码:
package cn.edu.qtech.csc.lcb.listviewdemo;
import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
//使用ListActivity--内部仅含有一个ListView
public class SimpleAdapterActivity extends ListActivity {
//数据线性表--List实际上是一个线性表的接口
List
(1)通过继承BaseAdapter可以定义自己的Adapter,可以将任何复杂组合的数据和资源,以任何想要的显示效果展示出来;
(2)BaseAdapter的使用方法与前述Adapter不一样,前述Adapter可以直接在其构造方法中进行数据的设置,但是BaseAdapter需要实现一个继承自BaseAdapter的子类,继承BaseAdapter之后,一般需要重写以下四个方法:getCount() ,getView() ,getItem(),getItemId()。
(3)getCount()和getView()这两个方法是自定义Adapter中最为重要的方法,只要同时重写这两个方法,ListView就能完全按要求显示。而 getItem()和getItemId()方法将会在调用ListView的响应方法的时候被调用到, 如果不调用其它响应方法,也可以不重写这2个方法;
(4)ListView绘制过程:系统在绘制ListView之前,(1)先调用getCount()方法来获取Item的个数;(2)之后每绘制一个Item就会调用一次getView()方法,在此方法内就可以将事先定义好的xml布局实例化,并返回一个View对象作为一个Item显示出来。
定义并填充存储底层数据的数据结构;
自定义一个继承自BaseAdapter的子类MyAdapter,并实现它的2个重要方法getCount()和getView():
getCount()返回底层数据结构的大小(item个数);
getView()中动态创建ListView中的一行,按需设置该行布局中的子控件的属性值和监听器。
创建MyAdapter对象;
设置Adapter;
需要的话,用setOnItemClick()方法添加点击行监听器。
package cn.edu.qtech.csc.lcb.listviewdemo;
import android.app.ListActivity;
import android.content.Context;
import android.os.Bundle;
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;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MyAdapterActivity extends ListActivity {
//ListView的底层数据对象变量
private List> list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//获取数据
list=getData();
//创建自定义的Adapter对象--内部数据与ListView各项的对应关系在自定义的Adapter中实现
MyAdapter adapter = new MyAdapter(this);
//为ListActivity中的ListView设置Adapter
setListAdapter(adapter);
}
//获取List数据对象
private List> getData(){
//List对象
List> list=new ArrayList>();
//List中存放的Map对象,由多个<键,值>对构成--一个Map对象对应ListView中的一行
Map map;
map=new HashMap();
map.put("title", "牛");
map.put("info", "食草动物,家畜");
map.put("img", R.drawable.cow);
list.add(map);
map=new HashMap();
map.put("title", "孔雀");
map.put("info", "鸟类,开屏很好看");
map.put("img", R.drawable.peacock);
list.add(map);
map=new HashMap();
map.put("title", "熊猫");
map.put("info", "珍稀,国宝");
map.put("img", R.drawable.panda);
list.add(map);
map=new HashMap();
map.put("title", "恐龙");
map.put("info", "爬行类,已灭绝");
map.put("img", R.drawable.dinosaur);
list.add(map);
map=new HashMap();
map.put("title", "神龙");
map.put("info", "神话中的动物");
map.put("img", R.drawable.dragon);
list.add(map);
map=new HashMap();
map.put("title", "北极熊");
map.put("info", "生活在极寒之地");
map.put("img", R.drawable.bear);
list.add(map);
//只要漏出一丁点,就要调用getView(...)显示该行
map=new HashMap();
map.put("title", "牛-2");
map.put("info", "食草动物,家畜-2");
map.put("img", R.drawable.cow);
list.add(map);
map=new HashMap();
map.put("title", "孔雀-2");
map.put("info", "鸟类,开屏很好看-2");
map.put("img", R.drawable.peacock);
list.add(map);
return list;
}
//自定义的Adapter类
/**Android系统更新ListView时需要调用相关的Adapter的方法:
* 1)更新前首先调用getCount()获取需要更新的行数,然后更新过程逐行进行
* 2)更新每行时,需要调用getView()获取当前行对应的View对象,
* Adapter需要在getView()方法中适时创建View对象,并对View对象填充需要显示的内容
* */
public final class MyAdapter extends BaseAdapter {
//实例化布局对象---用于实例化每行的布局->View对象
private LayoutInflater mInflater;
public MyAdapter(Context context){
this.mInflater = LayoutInflater.from(context);
}
//获取ListView的总行数
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int arg0) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
//ListView中一行对应的对象组合--容器类
//使用ViewHolder可以减少findViewById()的使用频率,方便数据访问
public final class ViewHolder{
public ImageView img;
public TextView title;
public TextView info;
public Button viewBtn;
}
//获取指定的一行所对应的View对象--不存在的话则创建之
// position--当前要显示的数据的位置(行号)
// convertView--可利用的以前的View对象(上下滚动时,利用旧View对象显示新内容),
// 如果此项为空,则需要动态创建新的View对象
// parent--父控件(上层的ListView)
@Override
public View getView(int position, View convertView, ViewGroup parent){
//实例化一行的布局
/*View rowview=mInflater.inflate(R.layout.myitem, null);
//找到行内的子控件
ImageView img=(ImageView)rowview.findViewById(R.id.img);
TextView title=(TextView)rowview.findViewById(R.id.title);
TextView info=(TextView)rowview.findViewById(R.id.info);
Button btn=(Button)rowview.findViewById(R.id.view_btn);
//子控件赋值
img.setBackgroundResource((Integer)list.get(position).get("img"));
title.setText((String)list.get(position).get("title"));
info.setText((String)list.get(position).get("info"));
//绑定该行中的Button对象的监听器
//创建监听器对象时, 用参数传递当前的行号
//每行中的Button建一个监听器对象,不同对象的position值不同
btn.setOnClickListener(new viewButtonClickListener(position)) ;
return rowview;*/
//本行对应的容器对象
ViewHolder holder = null;
//如果该行的View为空, 则动态创建新的View
//利用已有的View显示新数据,可以减少内存占用,优化响应速度
if (convertView == null) {
//先创建容器对象,以便后来向其中填充当前行中的控件对象
holder=new ViewHolder();
//实例化ListView的一行, root参数为空说明此View的父控件默认为为上层的ListView
convertView = mInflater.inflate(R.layout.myitem, null);
//获取内部的各个控件对象, 保存到容器对象中, 以后直接取来用即可--每个子控件对象只用一次findViewById()
holder.img = (ImageView)convertView.findViewById(R.id.img);
holder.title = (TextView)convertView.findViewById(R.id.title);
holder.info = (TextView)convertView.findViewById(R.id.info);
holder.viewBtn = (Button)convertView.findViewById(R.id.view_btn);
//设置容器对象为ListView当前行的Tag--建立容器类对象与ListView当前行的联系
convertView.setTag(holder);
}
else { //如果该行的View已经存在,则通过标记获取该行对应的对象
holder = (ViewHolder)convertView.getTag();
}
//设置该行内的控件对象对应的属性,Adapter的作用(List<--->ListView)--- 如果不用ViewHolder则需要频繁使用findViewByID
holder.img.setBackgroundResource((Integer)list.get(position).get("img"));
holder.title.setText((String)list.get(position).get("title"));
holder.info.setText((String)list.get(position).get("info"));
//绑定该行中的Button对象的监听器
//创建监听器对象时, 用参数传递当前的行号
//每行中的Button建一个监听器对象,不同对象的position值不同
holder.viewBtn.setOnClickListener(new viewButtonClickListener(position)) ;
return convertView;//返回当前行对应的View对象
}
}
//重写此方法---点击ListView一行时的回调函数--参数含义同前
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
String s=list.get(position).get("title").toString(); //获取该行的Map对象的指定属性的数据内容
Toast.makeText(MyAdapterActivity.this, s, Toast.LENGTH_SHORT).show();
}
//使用内部类实现ListView的每行中按钮的监听函数
//该监听器类会为ListView的每一行提供一个监听器对象,用来监听该行中按钮的点击事件
class viewButtonClickListener implements View.OnClickListener {
//记录按钮所在的行号
int position;
//必须使用自定义的构造函数---因为需要在此通过参数记录该监听器对象监听的行号
public viewButtonClickListener(int pos) {
position=pos;
}
@Override
public void onClick(View v) {
//获取该行的具体列的内容,并显示之
String info=list.get(position).get("info").toString();
Toast.makeText(MyAdapterActivity.this,info, Toast.LENGTH_SHORT).show();
}
}
}