Android Widget详解 —— ExpandableListView

一、第一种实现方法(通过SimpleExpandableListAdapter实现)

  SimpleExpandableListAdapter继承于BaseExpandableListAdapter。
  他负责把静态的group数据和child数据映射到XML文件中定的group和child视图上。
  SimpleExpandableListAdapter没有子类。
 

  SimpleExpandableListAdapter的构造函数如下:
    SimpleExpandableListAdapter(Context context, List<? extends Map<String, ?>> groupData, int groupLayout, 
    String[] groupFrom, int[] groupTo, 
    List<? extends List<? extends Map<String, ?>>> childData, int childLayout, String[] childFrom, int[] childTo)

    参数
    1、context:上下文。
    2、groupData:group数据,它是基于Map的list。Data里边的每一项都和 ListView里边的每一项对应。
        groupData里边的每一项都是一个Map类型,这个Map类里边包含了ListView每一行需要的数据。
    3、groupLayout :就是group布局文件id,可引用系统提供的,也可以自定义。
    4、groupFrom:这是个名字数组,每个名字是为了group在 ArrayList数组的每一个item索引Map<String,Object>的Object用的。
    5、groupTo:里面是一个为group显示用的TextView数组。这些 TextView是以id的形式来表示的。例如:Android.R.id.text1,这个text1在layout当中是可以索引的。
    6、childData:child数据,它是基于Map的list。Data里边的每一项都和 ListView里边的每一项对应。
        childData里边的每一项都是一个Map类型,这个Map类里边包含了ListView每一行需要的数据。
    7、childLayout :就是child的布局文件id,可引用系统提供的,也可以自定义。
    8、childFrom:这是个名字数组,每个名字是为了child在 ArrayList数组的每一个item索引Map<String,Object>的Object用的。
    9、childTo:里面是一个为child显示用的TextView数组。这些 TextView是以id的形式来表示的。例如:Android.R.id.text1,这个text1在layout当中是可以索引的。

  主要SimpleExpandableListAdapter提供了以下两个函数(增加的)    
  View        newChildView(boolean isLastChild, ViewGroup  parent)
  View        newGroupView(boolean isExpanded, ViewGroup parent)
  

效果图如下:

 

首先看下布局文件:在定义布局时,这里要定义三个布局文件,全在res/layout目录下,我先把布局文件附在下面,具体有什么用,我在下面会详细说明 

  main.xml

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="fill_parent"
 4     android:layout_height="fill_parent"
 5     android:orientation="vertical" >
 6 
 7      <ExpandableListView
 8         android:id="@+id/android:list"
 9         android:layout_width="fill_parent"
10         android:layout_height="wrap_content"       
11         android:drawSelectorOnTop="false" />
12     
13 </LinearLayout>

   groups.xml

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="fill_parent"
 4     android:layout_height="fill_parent"
 5     android:orientation="vertical" >
 6     <TextView
 7         android:id="@+id/group"
 8         android:layout_width="fill_parent"
 9         android:layout_height="fill_parent"
10         android:textSize="25sp"
11         android:paddingLeft="35px"
12         android:paddingTop="10px"
13         android:paddingRight="5px"
14         android:paddingBottom="10px"
15         android:text="No Data" />
16 </LinearLayout>

  childs.xml

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="fill_parent"
 4     android:layout_height="fill_parent"
 5     android:orientation="vertical" >
 6     <TextView
 7         android:id="@+id/child"
 8         android:layout_width="fill_parent"
 9         android:layout_height="fill_parent"
10         android:textSize="15sp"
11         android:paddingLeft="25px"
12         android:paddingTop="10px"
13         android:paddingRight="5px"
14         android:paddingBottom="10px"
15         android:text="No Data" />
16 </LinearLayout>

  首先,第一个布局文件main.xml是在主界面定义一个ExpandableListView ,在这里我们就要和在做ListView是的方法做下对比了,其实这个listView的显示类似。主要是用来显示ExpandableListView 下的数据。第二个布局文件是定义ExpandableListView 下的一级条目目录。在这里我只为一级条目目录添加一个textView控件。第三个布局文件是定义ExpandableListView 下的二级条目目录,和一级条目目录一样,布局文件里也只有一个TextView控件。  

 

 1 package com.yangyu;
 2 
 3 import java.util.ArrayList;
 4 import java.util.HashMap;
 5 import java.util.List;
 6 import java.util.Map;
 7 
 8 import android.app.ExpandableListActivity;
 9 import android.os.Bundle;
10 import android.view.View;
11 import android.widget.ExpandableListView;
12 import android.widget.SimpleExpandableListAdapter;
13 
14 public class ExpandableListViewDemo extends ExpandableListActivity {
15     @Override
16     public void onCreate(Bundle savedInstanceState) {
17         super.onCreate(savedInstanceState);
18         setContentView(R.layout.main);
19         //创建一级条目
20         List<Map<String,String>> groups = new ArrayList<Map<String,String>>();
21         //创建两个一级条目标题
22         Map<String,String> group1 = new HashMap<String, String>();
23         group1.put("group","我的好友");
24         Map<String,String> group2 = new HashMap<String, String>();
25         group2.put("group","同事");
26         groups.add(group1);
27         groups.add(group2);       
28         //创建一级条目下的二级条目
29         List<Map<String,String>> child1 = new ArrayList<Map<String,String>>();
30         //在一级条目目录下创建两个对应的二级条目目录
31         Map<String,String> childdata1 = new HashMap<String, String>();
32         childdata1.put("child","张三");
33         Map<String,String> childdata2 = new HashMap<String, String>();
34         childdata2.put("child","李四");
35         child1.add(childdata1);
36         child1.add(childdata2);
37         //同上
38         List<Map<String, String>> child2 = new ArrayList<Map<String, String>>();
39         Map<String, String> childdata3 = new HashMap<String, String>();
40         childdata3.put("child", "王五");
41         Map<String, String> childdata4 = new HashMap<String, String>();
42         childdata4.put("child", "赵六");
43         child2.add(childdata3);
44         child2.add(childdata4);
45         
46         // 将二级条目放在一个集合里,供显示时使用
47         List<List<Map<String, String>>> childs = new ArrayList<List<Map<String, String>>>();
48         childs.add(child1);
49         childs.add(child2);
50         
51         /**
52          * 使用SimpleExpandableListAdapter显示ExpandableListView
53          * 参数1.上下文对象Context
54          * 参数2.一级条目目录集合
55          * 参数3.一级条目对应的布局文件
56          * 参数4.fromto,就是map中的key,指定要显示的对象
57          * 参数5.与参数4对应,指定要显示在groups中的id
58          * 参数6.二级条目目录集合
59          * 参数7.二级条目对应的布局文件
60          * 参数8.fromto,就是map中的key,指定要显示的对象
61          * 参数9.与参数8对应,指定要显示在childs中的id
62          */
63         SimpleExpandableListAdapter adapter = new SimpleExpandableListAdapter(this,
64                         groups,R.layout.groups,new String[]{"group"},new int[]{R.id.group}, 
65                         childs,R.layout.childs,new String[]{"child"},new int[]{R.id.child});
66         setListAdapter(adapter);
67     }
68 
69     /**
70      * 当二级条目被点击时响应
71     */
72     @Override
73     public boolean onChildClick(ExpandableListView parent, View v,
74             int groupPosition, int childPosition, long id) {
75         // TODO Auto-generated method stub
76         return super.onChildClick(parent, v, groupPosition, childPosition, id);
77     }
78     
79     /**
80      * 设置哪个二级目录被默认选中
81     */
82     @Override
83     public boolean setSelectedChild(int groupPosition, int childPosition,
84             boolean shouldExpandGroup) {
85         // TODO Auto-generated method stub
86         return super.setSelectedChild(groupPosition, childPosition, shouldExpandGroup);
87     }
88     
89     /**
90     * 设置哪个一级目录被默认选中
91     */
92     @Override
93     public void setSelectedGroup(int groupPosition) {
94         // TODO Auto-generated method stub
95         super.setSelectedGroup(groupPosition);
96     }    
97 }

注意:android:id="@+id/android:list" 说明

可以是“@+id/list1234"
但程序里就需要改动啦

默认的 setListAdapter(adapter)
它是使用系统默认的id 必须是"@+id/android:list"

当自定义id时,程序里就不能直接 setListAdapter(adapter);
改动:
程序里:
ListView list=(ListView)findViewById(R.id.list1234);
...
....
list.setListAdapter(adapter)

 

二、第二种实现方法(通过BaseExpandableListAdapter实现)

 1、概述

  BaseExpandableListAdapter是ExpandableListAdapter的抽象基类,从一些数据中提供数据和视图给可折叠列表视图。

  所有继承本类的Adapters需要保证实现的getCombinedChildId(long, long)和 getCombinedGroupId(long)方法能正确地从View组或View子元素的ID中生成唯一的ID号。

  (译者注:组元素表示可折叠的列表项,子元素表示列表项展开后看到的多个子元素项。由于可折叠列表单纯寻找组元素和子元素的ID不是很方便,因此使用联合ID的方式来解决。于是有了getCombinedChildId()和getCombinedGroupId()方法。在andorid自带的ApiDomos的例子中有这个的代码:App/View/ExpandableList1)。

 2、实例

  项目需要展示一个通讯簿,通讯簿中的手机号码是分组的,要求勾选组时,自动勾选组下的手机号码,实现效果如下:


      我们这个实例主要讲的就是当点击一个分组的时候,分组里的所有人就默认的全部选中,有了这个功能我们在群发的时候就会给我们省去不少麻烦,这样我们就可以不用一个一个的选中了。那么我们就来看看这个效果是怎么样实现吗。

下面是实现步骤。

  (1)、新建类PhoneListItem,用于表示分组中的每一个手机号码。

 1 package com.yangyu.demo2;
 2 
 3 public class PhoneListItem {
 4 
 5     public String phone, name;
 6 
 7     public boolean checked;
 8 
 9     public PhoneListItem(String _name, String _phone, boolean _checked) {
10         name    = _name;
11         phone   = _phone;
12         checked = _checked;
13     }
14 }

  

 (2)、新建布局文件phone_list_item.xml,用于定义分组下面的每一条手机记录的页面布局。

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     android:layout_width="fill_parent"
 3     android:layout_height="wrap_content"
 4     android:layout_gravity="center_vertical"
 5     android:minHeight="40px"
 6     android:orientation="horizontal" >
 7 
 8     <CheckBox
 9         android:id="@+id/phone_check"
10         android:layout_width="wrap_content"
11         android:layout_height="wrap_content"
12         android:layout_marginLeft="35px"
13         android:checked="false"
14         android:focusable="false" />
15 
16     <TextView
17         android:id="@+id/phone_name"
18         android:layout_width="80dip"
19         android:layout_height="wrap_content"
20         android:layout_gravity="center_vertical" />
21 
22     <TextView
23         android:id="@+id/phone_number"
24         android:layout_width="wrap_content"
25         android:layout_height="wrap_content"
26         android:layout_gravity="center_vertical"
27         android:paddingLeft="10px"
28         android:textColor="?android:attr/textColorPrimary" />
29 </LinearLayout>

 这个主要就是我们在这里布局,你想要什么样的效果都可以,这里就是上面效果图的布局

  (3)、新建类PhoneGroup,用于表示一个分组。代码当中主要就是在一个分组里的内容,我们在用一个boolean值,这个就是说当你选中的时候是真,当你没有选中的时候是假,所以这个很主要,有了boolean值才能知道你是否选中。

 1 package com.yangyu.demo2;
 2 
 3 import java.util.List;
 4 
 5 public class PhoneGroup {
 6     public String title;
 7 
 8     private boolean checked;
 9 
10     public List<PhoneListItem> children;
11 
12     public PhoneGroup(String title, boolean checked,List<PhoneListItem> children) {
13         this.title = title;
14         setChecked(checked);
15         this.children = children;
16     }
17 
18     public boolean getChecked() {
19         return checked;
20     }
21 
22     public void setChecked(boolean b) {
23         checked = b;
24         if (children != null && children.size() > 0) {// 若children不为空,循环设置children的checked
25             for (PhoneListItem each : children) {
26                 each.checked = checked;
27             }
28         }
29     }
30 }

 

  (4)、新建Activity布局文件phone_browser.xml,用于定义通讯簿的展现页面。

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     android:layout_width="fill_parent"
 3     android:layout_height="fill_parent"
 4     android:orientation="vertical" >
 5 
 6     <ExpandableListView
 7         android:id="@+id/phonelist"
 8         android:layout_width="fill_parent"
 9         android:layout_height="0dip"
10         android:layout_weight="1" />
11 
12 </LinearLayout>

 

(5)、新建类ExpandableListViewDemo2,用于呈现通讯簿的Activity页面。

 1 package com.yangyu.demo2;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 import android.app.Activity;
 7 import android.os.Bundle;
 8 import android.view.View;
 9 import android.widget.ExpandableListView;
10 
11 import com.yangyu.R;
12 import com.yangyu.demo2.PhoneGroupAdapter.ExpandableListHolder;
13 
14 public class ExpandableListViewDemo2 extends Activity implements ExpandableListView.OnGroupClickListener,
15         ExpandableListView.OnChildClickListener {
16     private List<PhoneGroup> groups;
17 
18     private PhoneGroupAdapter exlist_adapter = null;
19 
20     private ExpandableListView exlist;
21 
22     public void onCreate(Bundle savedInstanceState) {
23         super.onCreate(savedInstanceState);
24         // 加载layout
25         setContentView(R.layout.phone_browser);
26         // 取得listview
27         exlist = (ExpandableListView)findViewById(R.id.phonelist);
28         // 调用init方法,这个方法主要是,初始化一些数据
29         init();
30         // 构建expandablelistview的适配器
31         exlist_adapter = new PhoneGroupAdapter(this, groups);
32         exlist.setOnChildClickListener(this);
33         exlist.setAdapter(exlist_adapter); // 绑定视图-适配器
34     }
35 
36     private void init() {
37         groups = new ArrayList<PhoneGroup>();
38         // 构建List用作group1的子项
39         List<PhoneListItem> group1_children = new ArrayList<PhoneListItem>();
40         // 往List中添加内容
41         PhoneListItem item = new PhoneListItem("张三", "1308763994", false);
42         group1_children.add(item);
43         
44         item = new PhoneListItem("李四", "1308763994", false);
45         group1_children.add(item);
46 
47         item = new PhoneListItem("王五", "1308763994", false);
48         group1_children.add(item);
49         // 拼装成 PhoneGroup
50         PhoneGroup phonegroup1 = new PhoneGroup("group1",false,group1_children);
51 
52         // ------把前面的代码复制一遍,再添加一个组group2
53         // 构建List用作group2的子项
54         List<PhoneListItem> group2_children = new ArrayList<PhoneListItem>();
55         // 往List中添加内容
56         item = new PhoneListItem("赵六", "1589065423", false);
57         group2_children.add(item);
58         
59         item = new PhoneListItem("田七", "1589065423", false);
60         group2_children.add(item);
61 
62         item = new PhoneListItem("牛二", "1589065423", false);
63         group2_children.add(item);
64         // 拼装成 PhoneGroup
65         PhoneGroup phonegroup2 = new PhoneGroup("group2", false,group2_children);
66 
67         // 添加进groups数组
68         groups.add(phonegroup1);
69         groups.add(phonegroup2);
70     }
71 
72     // 当分组行被点击时,让分组呈现“选中/取消选中”状态。
73     @Override
74     public boolean onChildClick(ExpandableListView parent,
75             View v,int groupPosition, int childPosition, long id) {
76         PhoneGroupAdapter.ExpandableListHolder holder = (ExpandableListHolder) v.getTag();
77         holder.chkChecked.setChecked(!holder.chkChecked.isChecked());
78         groups.get(groupPosition).children.get(childPosition).checked =!
79                 groups.get(groupPosition).children.get(childPosition).checked;
80         return false;
81     }
82 
83     @Override
84     public boolean onGroupClick(ExpandableListView parent, View v,
85             int groupPosition, long id) {        
86         return false;
87     }
88 }

 

 (6)、新建类PhoneGroupAdapter,继承自BaseExpandableListAdapter

  1 package com.yangyu.demo2;
  2 
  3 import java.util.List;
  4 
  5 import android.content.Context;
  6 import android.view.LayoutInflater;
  7 import android.view.View;
  8 import android.view.View.OnClickListener;
  9 import android.view.ViewGroup;
 10 import android.widget.BaseExpandableListAdapter;
 11 import android.widget.CheckBox;
 12 import android.widget.TextView;
 13 
 14 import com.yangyu.R;
 15 
 16 public class PhoneGroupAdapter extends BaseExpandableListAdapter {
 17     class ExpandableListHolder { // 定义一个内部类,用于保存listitem的3个子视图引用,2个textview和1个checkbox
 18         TextView tvName;
 19         TextView tvPhone;
 20         CheckBox chkChecked;
 21     }
 22 
 23     private Context context; // 父activity
 24     private LayoutInflater mChildInflater; // 用于加载listitem的布局xml
 25     private LayoutInflater mGroupInflater; // 用于加载group的布局xml
 26     private List<PhoneGroup> groups; // 所有group
 27     // 构造方法:参数c - activity,参数group - 所有group
 28 
 29     public PhoneGroupAdapter(Context c, List<PhoneGroup> groups) {
 30         context = c;
 31         mChildInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 32         mGroupInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 33         this.groups = groups;
 34     }
 35 
 36     @Override
 37     public Object getChild(int arg0, int arg1) {// 根据组索引和item索引,取得listitem         
 38         return groups.get(arg0).children.get(arg1);
 39     }
 40 
 41     @Override
 42     public long getChildId(int arg0, int arg1) {// 返回item索引
 43         return arg1;
 44     }
 45 
 46     @Override
 47     public int getChildrenCount(int groupPosition) {// 根据组索引返回分组的子item数
 48         return groups.get(groupPosition).children.size();
 49     }
 50 
 51     @Override
 52     public Object getGroup(int groupPosition) {// 根据组索引返回组
 53         return groups.get(groupPosition);
 54     }
 55 
 56     @Override
 57     public int getGroupCount() {// 返回分组数
 58         return groups.size();
 59     }
 60 
 61     @Override
 62     public long getGroupId(int groupPosition) {// 返回分组索引
 63         return groupPosition;
 64     }
 65 
 66     @Override
 67     public View getGroupView(int position, boolean isExpanded, View view,
 68             ViewGroup parent) {// 根据组索引渲染"组视图"
 69         ExpandableListHolder holder = null; // 清空临时变量holder
 70         if (view == null) { // 判断view(即view是否已构建好)是否为空
 71 
 72             // 若组视图为空,构建组视图。注意flate的使用,R.layout.browser_expandable_list_item代表了
 73             // 已加载到内存的browser_expandable_list_item.xml文件
 74             view = mGroupInflater.inflate(R.layout.phone_list_item, null);
 75             // 下面主要是取得组的各子视图,设置子视图的属性。用tag来保存各子视图的引用
 76             holder = new ExpandableListHolder();
 77             // 从view中取得textView
 78             holder.tvName = (TextView) view.findViewById(R.id.phone_name);
 79             // 从view中取得textview
 80             holder.tvPhone = (TextView) view.findViewById(R.id.phone_number);
 81             // 从view中取得checkbox
 82             holder.chkChecked = (CheckBox) view.findViewById(R.id.phone_check);
 83             // holder.chkChecked.setEnabled(false);//禁用checkbox
 84             // 把checkbox、textview的引用保存到组视图的tag属性中
 85             view.setTag(holder);
 86         } else { // 若view不为空,直接从view的tag属性中获得各子视图的引用
 87             holder = (ExpandableListHolder) view.getTag();
 88         }
 89         // 对应于组索引的组数据(模型)
 90         PhoneGroup info = this.groups.get(position);
 91         if (info != null) {
 92             // 根据模型值设置textview的文本
 93             holder.tvName.setText(info.title);
 94             // 根据模型值设置checkbox的checked属性
 95             holder.chkChecked.setChecked(info.getChecked());
 96             holder.chkChecked.setTag(info);
 97             holder.chkChecked.setOnClickListener(new OnClickListener() {
 98                 @Override
 99                 public void onClick(View v) {
100                     PhoneGroup group = (PhoneGroup) v.getTag();
101                     group.setChecked(!group.getChecked());
102                     notifyDataSetChanged();
103                 }
104             });
105         }        
106         return view;
107     }
108 
109     // 行渲染方法
110     @Override
111     public View getChildView(int groupPosition, int childPosition,
112             boolean isLastChild, View convertView, ViewGroup parent) {
113         ExpandableListHolder holder = null; // 清空临时变量
114         if (convertView == null) { // 若行未初始化
115            // 通过flater初始化行视图
116            convertView = mChildInflater.inflate(R.layout.phone_list_item, null);
117            // 并将行视图的3个子视图引用放到tag中
118            holder = new ExpandableListHolder();
119            holder.tvName = (TextView) convertView.findViewById(R.id.phone_name);
120            holder.tvPhone = (TextView) convertView.findViewById(R.id.phone_number);
121            holder.chkChecked = (CheckBox) convertView.findViewById(R.id.phone_check);
122            // holder.chkChecked.setEnabled(false);
123            convertView.setTag(holder);
124         } else { // 若行已初始化,直接从tag属性获得子视图的引用
125             holder = (ExpandableListHolder) convertView.getTag();
126         }
127         // 获得行数据(模型)
128         PhoneListItem info = this.groups.get(groupPosition).children.get(childPosition);
129         if (info != null) {
130             // 根据模型数据,设置行视图的控件值
131             holder.tvName.setText(info.name);
132             holder.tvPhone.setText(info.phone);
133             holder.chkChecked.setChecked(info.checked);
134             holder.chkChecked.setTag(info);
135             holder.chkChecked.setOnClickListener(new OnClickListener() {
136                 @Override
137                 public void onClick(View v) {
138                     CheckBox check = (CheckBox) v;
139                     PhoneListItem item = (PhoneListItem) v.getTag();
140                     item.checked = !item.checked;
141                 }
142             });
143         }
144         return convertView;
145     }
146 
147     @Override
148     public boolean hasStableIds() {// 行是否具有唯一id
149         return false;
150     }
151 
152     @Override
153     public boolean isChildSelectable(int groupPosition, int childPosition) {// 行是否可选
154         return true;
155     }
156 }

 

  源码下载地址

 

posted on 2012-08-20 16:41  〃ωǒ系﹄条噚氺dē魚ぐ  阅读(577)  评论(0)    收藏  举报

导航