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) 收藏 举报
浙公网安备 33010602011771号