详解listview的四种适配器模式

ListView是Android开发中比较常用的一个组件,它以列表的形式展示具体内容,并且能够根据数据的长度自适应显示。比如说我们手机里的通讯录就使用到了ListView显示联系人信息。ListView同时也是所有Android UI控件中最为麻烦的控件,之所以麻烦就是因为它的各种的适配器特别麻烦。

创建ListView有两种方式:

1、直接创建ListView

2、让Activity继承ListActivity

列表的显示需要三个元素:
1.ListVeiw :用来展示列表的View。
2.适配器 :用来把数据映射到ListView上的中介。
3.数据集   :具体的将被映射的字符串,图片,或者基本组件。

根据列表的适配器类型,列表分为四种,ArrayAdapterSimpleAdapterSimpleCursorAdapter以及自定义Adapter
其中以ArrayAdapter最为简单,只能展示一行字。SimpleAdapter有最好的扩充性,可以自定义出各种效果。 SimpleCursorAdapter可以认为是SimpleAdapter对数据库的简单结合,可以方面的把数据库的内容以列表的形式展示出来。

下面我们就通过几个简单的例子讲解一下通过各种适配器来构建ListView。

1、1使用ArrayAdatpter构建ListView

新建一个Android项目:ListViewDemo,同时新建一个类ListViewDemoActivity继承Activity,代码如下:

package com.liuzhichao.listview; 
import android.app.Activity; 
import android.os.Bundle; 
import android.widget.ArrayAdapter; 
import android.widget.ListView; 
public class ListViewDemoActivity extends Activity { 
    //定义一个ListView 
    private ListView mListView; 
    //定义一个String数组,数组里的数据就是ListView里的一项 
    private String\[\] items={"1、ArrayAdapter_List","2、SimpleAdapter_List"
                        ,"3、SimpleCursorAdapter_List","4、MyAdapter_List"}; 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        //new一个ListView 
        mListView = new ListView(this); 
        //通过setAdapter构建一个ArrayAdapter将items与mListView"绑定" 
        mListView.setAdapter(new ArrayAdapter<String>(this, 
                android.R.layout.simple_list_item_1, items)); 
        //显示mListView 
        setContentView(mListView); 
    } 
}

上面代码使用了ArrayAdapter(Context context, int textViewResourceId, List objects)来装配数据,要装配这些数据就需要一个连接ListView视图对象和数组数据的适配器来两者的适配工作,ArrayAdapter的构造需要三个参数,依次为this,布局文件(注意这里的布局文件描述的是列表的每一行的布局,android.R.layout.simple_list_item_1是系统定义好的布局文件只显示一行文字,数据源(一个List集合)。同时用setAdapter()完成适配的最后工作。运行后的现实结构如下图:

arrayAdapterList

1、2使用SimpleAdapter构建ListView

使用simpleAdapter的数据用一般都是HashMap构成的List,list的每一节对应ListView的每一行。HashMap的每个键值数据映射到布局文件中对应id的组件上。下面我们使用SimpleAdapter模拟一个通讯录。

因为系统没有对应的布局文件可用,我们可以自己定义一个布局info.xml:

<?xml version="1.0" encoding="utf-8"?> 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" > 
    <ImageView 
        android:id="@+id/info_img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="3dp"
        android:src="@drawable/ic_launcher"/> 
    <TextView 
        android:id="@+id/info_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/info_img"
        android:layout_marginTop="5dp"/> 
    <TextView 
        android:id="@+id/info_phone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/info_name"
        android:layout_toRightOf="@id/info_img"
        android:layout_alignBaseline="@id/info_img"/> 
     <TextView 
        android:id="@+id/info_region"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/info_phone"
        android:layout_alignBaseline="@id/info_phone"
        android:layout_marginLeft="10dip"/> 
</RelativeLayout>

新建一个SimpleAdapterListView继续Activity,代码如下:

package com.liuzhichao.listview; 
import java.util.ArrayList; 
import java.util.HashMap; 
import java.util.Map; 
import android.app.Activity; 
import android.os.Bundle; 
import android.widget.ListView; 
import android.widget.SimpleAdapter; 
public class SimpleAdapterListView extends Activity{ 
    //分别定义通讯录中的用户名、电话、地区等信息 
    private String\[\] info_Names={"史珍香","赖月京","秦寿生","刘产","扬伟","范剑"}; 
    private String\[\] info_Phones={"13844445144","13844444444","13444445144","13544445144","13644445144","13744445144"}; 
    private String\[\] info_Regions={"火星","水星","木星","月球","美国","未知地区"}; 
    //定义一个ArrayList数组,每一条数据对应通讯录中的一个联系人信息 
    private ArrayList<Map<String,Object>> mInfos= new ArrayList<Map<String,Object>>(); 
    //定义一个ListView 
    private ListView mListView; 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        // TODO Auto-generated method stub 
        super.onCreate(savedInstanceState); 
        //new一个ListView 
        mListView = new ListView(this); 
        //添加联系人信息 
        for(int i=0;i<info_Names.length;i++){ 
             Map<String,Object> item = new HashMap<String,Object>(); 
             item.put("img", R.drawable.contact_img); 
             item.put("name", info_Names\[i\]); 
             item.put("phone", info_Phones\[i\]); 
             item.put("region", info_Regions\[i\]); 
             mInfos.add(item); 
        } 
        //定义一个SimpleAdapter 
        SimpleAdapter adapter = new SimpleAdapter(this, mInfos, R.layout.info, 
                new String\[\]{"img","name","phone","region"}, 
                new int\[\]{R.id.info_img,R.id.info_name,R.id.info_phone,R.id.info_region}); 
        //设置mListView的适配器为adapter 
        mListView.setAdapter(adapter); 
        setContentView(mListView); 
    } 
}

使用simpleAdapter的数据用一般都是HashMap构成的List,list的每一节对应ListView的每一行。HashMap的每个键 值数据映射到布局文件中对应id的组件上。因为系统没有对应的布局文件可用,我们可以自己定义一个布局info.xml。运行效果如下图:

我相信肯定有人对new SimpleAdapter()中的参数有一些疑问,下面我们就来看一下SimpleAdapter的构造函数:SimpleAdapter(Context context, List > data, int resource, String[] from, int[] to) ,context相信不用解释了,假设将SimpleAdapter用于ListView。那么ListView的每一个列表项就是resource参数值指定的布局。而data参数就是要加载到ListView中的数据。那么fromto呢?在加载列表项时,需要通过组件的id和data参数中List元素中的Map对象对应。因此,from参数为Map对象的key,而to表示组件的id,例如,本例中的参数值为from=new String[]{“img”,”name”,”phone”,”region”},to=new int[]{R.id.info_img,R.id.info_name,R.id.info_phone,R.id.info_region}),意思就是将Map对象中key为”img”的value绑定到R.id.info_img,将Map对象中key为”name”的value绑定到R.id.info_name,phone、region也类似。所以fromto中的参数是一一对应的关系。同时 from又是对应的Map中的key,to又是对应布局文件中相应组件的ID。

1、3使用SimpleCursorAdapter 构建ListView

相比SimpleAdapter,SimpleCursorAdapter 就是方便把从游标得到的数据进行列表显示,并可以把指定的列映射到对应的组件中。SimpleCursorAdapter的构造函数与SimpleAdapter的区别就是多了一个Cursor c:[SimpleCursorAdapter](http://developer.android.com/reference/android/widget/SimpleCursorAdapter.html#SimpleCursorAdapter(android.content.Context, int, android.database.Cursor, java.lang.String[], int[]))(Context context, int layout, Cursor c, String[] from, int[] to),Cursor就是游标,如果你不清楚游标的概念,就想像成数据查询后的一个结果集。下面是一个通过SimpleCursorAdapter 使用ListView显示系统通讯录中联系人的例子。

package com.liuzhichao.listview; 
import android.app.Activity; 
import android.database.Cursor; 
import android.os.Bundle; 
import android.provider.ContactsContract.Contacts; 
import android.widget.ListView; 
import android.widget.SimpleCursorAdapter; 
public class SimpleCursorAdapterActivity extends Activity { 
    private ListView mListView; 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        // TODO Auto-generated method stub 
        super.onCreate(savedInstanceState); 
        mListView = new ListView(this); 
         Cursor cursor=this.getContentResolver().query(Contacts.CONTENT_URI, null, null, null, null);   
         SimpleCursorAdapter adapter=new SimpleCursorAdapter(this, android.R.layout.simple_expandable_list_item_2, cursor,    
                    new String\[\]{Contacts.DISPLAY_NAME}, 
                    new int\[\]{android.R.id.text1});   
        mListView.setAdapter(adapter); 
        setContentView(mListView); 
    } 
}

注意:在读取系统通讯录时,需要如下权限:

<uses-permission android:name="android.permission.READ_CONTACTS"/>

左图上是系统通讯录中的联系人,右图是运行的结果。

1、4使用自定义Adapter构建ListView

当我们使用系统自带的ArrayAdapter、SimpleAdapter和SimpleCursorAdapter适配器时,对于事件的响应只能局限在一个行单位。假设一行里面有一个按钮和一个图片控件,它们之间的响应操作是不一样的。若采用系统自带的适配器,就不能精确到每个控件的响应事件。这时,我们一般采取自定义适配器来实现这个比较精确地请求。我们再新建一个MyAdapterListActivity继承Activity,使用自定义适配器来实现SimpleAdapterListView中的效果,并新增一个多选框和按钮。效果如下:

MyAdapterListActivity.java:

package com.liuzhichao.listview; 
import java.util.ArrayList; 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 
import android.app.Activity; 
import android.app.AlertDialog; 
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.CheckBox; 
import android.widget.CompoundButton; 
import android.widget.ImageView; 
import android.widget.ListView; 
import android.widget.TextView; 
public class MyAdapterListActivity extends Activity { 
    //分别定义通讯录中的用户名、电话、地区等信息 
        private String\[\] info_Names={"史珍香","赖月京","秦寿生","刘产","扬伟","范剑"}; 
        private String\[\] info_Phones={"13844445144","13844444444","13444445144","13544445144","13644445144","13744445144"}; 
        private String\[\] info_Regions={"火星","水星","木星","月球","美国","未知"}; 
        //定义一个ArrayList数组,每一条数据对应通讯录中的一个联系人信息 
        private ArrayList<Map<String,Object>> mInfos= new ArrayList<Map<String,Object>>(); 
        //定义一个ListView 
        private ListView mListView; 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        // TODO Auto-generated method stub 
        super.onCreate(savedInstanceState); 
        //new一个ListView 
                mListView = new ListView(this); 
                //添加联系人信息 
                for(int i=0;i<info_Names.length;i++){ 
                     Map<String,Object> item = new HashMap<String,Object>(); 
                     item.put("img", R.drawable.contact_img); 
                     item.put("name", info_Names\[i\]); 
                     item.put("phone", info_Phones\[i\]); 
                     item.put("region", info_Regions\[i\]); 
                     mInfos.add(item); 
                } 
                MyAdapter adapter = new MyAdapter(this, mInfos); 
                mListView.setAdapter(adapter); 
                setContentView(mListView); 
    } 
    private class MyAdapter extends BaseAdapter { 
        private Context context;                        //运行上下文    
        private List<Map<String, Object>> listItems;    //联系人信息集合    
        private LayoutInflater listContainer;           //视图容器    
        private boolean\[\] hasChecked;                   //记录联系人选中状态    
        public final class ListItemView{                //自定义控件集合      
                public ImageView img;      
                public TextView name;      
                public TextView phone; 
                public TextView region; 
                public CheckBox check;    
                public Button detail;           
         }      
        public MyAdapter(Context context, List<Map<String, Object>> listItems) {    
            this.context = context;             
            listContainer = LayoutInflater.from(context);   //创建视图容器并设置上下文    
            this.listItems = listItems;    
            hasChecked = new boolean\[getCount()\];    
        }    
        public int getCount() { 
            return listItems.size(); 
        } 
        public Object getItem(int position) { 
            return null; 
        } 
        public long getItemId(int position) { 
            return 0; 
        } 
        public View getView(int position, View convertView, ViewGroup parent) { 
            final int selectID = position; 
            ListItemView  listItemView = null;    
            if (convertView == null) {    
                listItemView = new ListItemView();     
                //获取list_item布局文件的视图    
                convertView = listContainer.inflate(R.layout.myinfo, null);    
                //获取控件对象    
                listItemView.img = (ImageView)convertView.findViewById(R.id.info_img);    
                listItemView.name = (TextView)convertView.findViewById(R.id.info_name);    
                listItemView.phone = (TextView)convertView.findViewById(R.id.info_phone);  
                listItemView.region = (TextView)convertView.findViewById(R.id.info_region); 
                listItemView.detail= (Button)convertView.findViewById(R.id.btn);    
                listItemView.check = (CheckBox)convertView.findViewById(R.id.checkBox);    
                //设置控件集到convertView    
                convertView.setTag(listItemView);   
              //设置联系人信息 
                listItemView.img.setBackgroundResource((Integer) listItems.get(    
                        position).get("img"));   
                listItemView.name.setText((String) listItems.get(    
                        position).get("name"));  
                listItemView.phone.setText((String) listItems.get(    
                        position).get("phone"));  
                listItemView.region.setText((String) listItems.get(    
                        position).get("region"));  
                //More按钮事件 
                listItemView.detail.setOnClickListener(new View.OnClickListener() { 
                    public void onClick(View v) { 
                        showDetailInfo(selectID); 
                    } 
                }); 
             // 注册多选框状态事件处理    
                listItemView.check    
                        .setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() {    
                            public void onCheckedChanged(CompoundButton buttonView,    
                                    boolean isChecked) {    
                                //记录联系人选中状态    
                                checkedChange(selectID);    
                            }    
                });    
            }else {    
                listItemView = (ListItemView)convertView.getTag();    
            }  
            return convertView; 
        } 
        /**   
         * 记录勾选了哪个联系人  
         * @param checkedID 选中的联系人序号   
         */ 
        private void checkedChange(int checkedID) {    
            hasChecked\[checkedID\] = !hasChecked(checkedID);    
        } 
        /**   
         * 判断联系人是否选择   
         * @param checkedID 联系人序号   
         * @return 返回是否选中状态   
         */  
        public boolean hasChecked(int checkedID) {    
            return hasChecked\[checkedID\];    
        }  
        /**   
         * 显示物品详情   
         * @param clickID   
         */  
        private void showDetailInfo(int clickID) {    
            new AlertDialog.Builder(context) 
            .setIcon(Integer.parseInt(listItems.get(clickID).get("img").toString())) 
            .setTitle(listItems.get(clickID).get("name")+"详细信息")    
            .setMessage("电话:"+listItems.get(clickID).get("phone").toString()+" 地区:"+listItems.get(clickID).get("region").toString())                  
            .setPositiveButton("确定", null)    
            .show();    
        }  
    } 
}

myinfo.xml:

<?xml version="1.0" encoding="utf-8"?> 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" > 
    <ImageView 
        android:id="@+id/info_img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="3dp"/> 
    <TextView 
        android:id="@+id/info_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/info_img"
        android:layout_marginTop="5dp"/> 
    <TextView 
        android:id="@+id/info_phone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/info_name"
        android:layout_toRightOf="@id/info_img"
        android:layout_alignBaseline="@id/info_img"/> 
     <TextView 
        android:id="@+id/info_region"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/info_phone"
        android:layout_alignBaseline="@id/info_phone"
        android:layout_marginLeft="10dip"/> 
     <CheckBox 
         android:id="@+id/checkBox"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginLeft="5dp"
         android:layout_alignBaseline="@id/info_img"
         android:layout_toRightOf="@id/info_region"/> 
     <Button 
         android:id="@+id/btn"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignBaseline="@id/checkBox"
         android:layout_toRightOf="@id/checkBox"
         android:text="More..."
         /> 
</RelativeLayout>

2、让Activity继承ListActivity构建ListView
如果程序的窗口仅仅需要显示一个列表,则可以让Activity直接继续ListActivity来实现。ListActivity的子类无需调用setContentView()方法来显示某个界面,而是可以直接传入一个Adapter,ListActivity的子类就可以呈现出一个列表。

package com.liuzhichao.listview; 
import java.util.ArrayList; 
import java.util.HashMap; 
import java.util.Map; 
import android.app.ListActivity; 
import android.os.Bundle; 
import android.widget.SimpleAdapter; 
public class ListActivityList extends ListActivity { 
    //分别定义通讯录中的用户名、电话、地区等信息 
        private String\[\] info_Names={"***","***","***","***","***","***"}; 
        private String\[\] info_Phones={"13844445144","13844444444","13444445144","13544445144","13644445144","13744445144"}; 
        private String\[\] info_Regions={"火星","水星","木星","月球","美国","未知地区"}; 
        //定义一个ArrayList数组,每一条数据对应通讯录中的一个联系人信息 
        private ArrayList<Map<String,Object>> mInfos= new ArrayList<Map<String,Object>>(); 
        @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        // TODO Auto-generated method stub 
        super.onCreate(savedInstanceState); 
        //添加联系人信息 
                for(int i=0;i<info_Names.length;i++){ 
                     Map<String,Object> item = new HashMap<String,Object>(); 
                     item.put("img", R.drawable.contact_img); 
                     item.put("name", info_Names\[i\]); 
                     item.put("phone", info_Phones\[i\]); 
                     item.put("region", info_Regions\[i\]); 
                     mInfos.add(item); 
                } 
                //定义一个SimpleAdapter 
                SimpleAdapter adapter = new SimpleAdapter(this, mInfos, R.layout.info, 
                        new String\[\]{"img","name","phone","region"},  
                        new int\[\]{R.id.info_img,R.id.info_name,R.id.info_phone,R.id.info_region}); 
                //本例中没有声明一个ListView,但通过继承ListActivity使用setListAdapter然后传入一个适配器即可直接显示一个列表。 
                setListAdapter(adapter); 
    } 
}