/*
* Android开发之ExpandableListView
* 北京Android俱乐部群:167839253
*Created on: 2012-7-23
*Author: blueeagle
* Email:liujiaxiang@gmail.com
*/
有时候,使用ListView并不能满足应用程序所需要的功能。有些应用程序需要多组ListView,这时候我们就要使用一种新的控件ExpandableListView——可以扩展的ListView。它的作用就是将ListView进行分组。就好像我们使用QQ的时候,有“我的好友”,“陌生人”,“黑名单”一样,点击一下会扩展开,再点击一下又会收缩回去。
ExpandableListView是一个垂直滚动显示两级列表项的视图,与ListView不同的是,它可以有两层:每一层都能够被独立的展开并显示其子项。这些子项来自于与该视图关联的ExpandableListAdapter。
每一个可以扩展的列表项的旁边都有一个指示符(箭头)用来说明该列表项目前的状态(这些状态一般是已经扩展开的列表项,还没有扩展开的列表项,子列表项和最后一个子列表项)。可以使用方法:setChildIndicator(Drawable),setGroupIndicator(Drawable)(或者相应的XML文件的属性) 去设置这些指示符的样式。当然也可以使用默认的指示符。布android.R.layout.simple_expandable_list_item_1,android.R.layout.simple_expandable_list_item_2
和ListView一样,ExpandableListView也是一个需要Adapter作为桥梁来取得数据的控件。一般适用于ExpandableListView的Adapter都要继承BaseExpandableListAdapter这个类,并且必须重载getGroupView和getChildView这两个最为重要的方法。
BaseExpandableListAdapter的主要重载方法如下:
public abstract ObjectgetChild (int groupPosition, int childPosition)
取得与指定分组、指定子项目关联的数据.
参数
groupPosition 包含子视图的分组的位置.
childPosition 指定的分组中的子视图的位置.
返回
与子视图关联的数据.
public abstract long getChildId (int groupPosition, intchildPosition)
取得给定分组中给定子视图的ID. 该组ID必须在组中是唯一的.必须不同于其他所有ID(分组及子项目的ID).
参数
groupPosition 包含子视图的分组的位置.
childPosition 要取得ID的指定的分组中的子视图的位置.
返回
与子视图关联的ID.
public abstract View getChildView (int groupPosition, intchildPosition, boolean isLastChild, View convertView, ViewGroup parent)
取得显示给定分组给定子位置的数据用的视图.
参数
groupPosition 包含要取得子视图的分组位置.
childPosition 分组中子视图(要返回的视图)的位置.
isLastChild 该视图是否为组中的最后一个视图.
convertView 如果可能,重用旧的视图对象.使用前你应该保证视图对象为非空,并且是否是合适的类型.如果该对象不能转换为可以正确显示数据的视图,该方法就创建新视图.不保证使用先前由 getChildView(int, int,boolean, View, ViewGroup)创建的视图.
parent 该视图最终从属的父视图.
返回
指定位置相应的子视图.
public abstract int getChildrenCount (int groupPosition)
取得指定分组的子元素数.
参数
groupPosition 要取得子元素个数的分组位置.
返回
指定分组的子元素个数.
public abstract long getCombinedChildId (long groupId, long childId)
取得一览中可以唯一识别子条目的 ID(包括分组ID和子条目ID).可扩展列表要求每个条目 (分组条目和子条目)具有一个可以唯一识别列表中子条目和分组条目的ID. 该方法根据给定子条目ID和分组条目ID返回唯一识别ID.另外,如果 hasStableIds() 为真,该函数返回的ID必须是固定不变的.
参数
groupId 包含子条目ID的分组条目ID.
childId 子条目的ID.
返回
可以在所有分组条目和子条目中唯一识别该子条目的ID(可能是固定不变的).
public abstract long getCombinedGroupId (long groupId)
取得一览中可以唯一识别子条目的 ID(包括分组ID和子条目ID).可扩展列表要求每个条目 (分组条目和子条目)具有一个可以唯一识别列表中子条目和分组条目的ID. 该方法根据给定子条目ID和分组条目ID返回唯一识别ID.另外,如果 hasStableIds() 为真,该函数返回的ID必须是固定不变的.
参数
groupId 分组条目ID.
返回
可以在所有分组条目和子条目中唯一识别该分组条目的ID(可能是固定不变的).
public abstract Object getGroup (int groupPosition)
取得与给定分组关联的数据.
参数
groupPosition 分组的位置.
返回
指定分组的数据.
public abstract int getGroupCount ()
取得分组数.
返回
分组数.
public abstract long getGroupId (int groupPosition)
取得指定分组的ID.该组ID必须在组中是唯一的.必须不同于其他所有ID(分组及子项目的ID).
参数
groupPosition 要取得ID的分组位置.
返回
与分组关联的ID.
public abstract View getGroupView (int groupPosition, booleanisExpanded, View convertView, ViewGroup parent)
取得用于显示给定分组的视图. 这个方法仅返回分组的视图对象, 要想获取子元素的视图对象,就需要调用 getChildView(int, int, boolean, View, ViewGroup).
参数
groupPosition 决定返回哪个视图的组位置 .
isExpanded 该组是展开状态还是收起状态 .
convertView 如果可能,重用旧的视图对象.使用前你应该保证视图对象为非空,并且是否是合适的类型.如果该对象不能转换为可以正确显示数据的视图,该方法就创建新视图.不保证使用先前由 getGroupView(int, boolean,View, ViewGroup)创建的视图.
parent 该视图最终从属的父视图.
返回
指定位置相应的组视图.
public abstract boolean hasStableIds ()
是否指定分组视图及其子视图的ID对应的后台数据改变也会保持该ID.
返回
是否相同的ID总是指向同一个对象.
public abstract boolean isChildSelectable (int groupPosition, intchildPosition)
指定位置的子视图是否可选择.
参数
groupPosition 包含要取得子视图的分组位置.
childPosition 分组中子视图的位置.
返回
是否子视图可选择.
注意:
在XML布局文件中,如果ExpandableListView上一级视图的大小没有严格定义的话,则不能对ExpandableListView的android:layout_height 属性使用wrap_content值。 (例如,如果上一级视图是ScrollView的话,则不应该指定wrap_content的值,因为它可以是任意的长度。不过,如果ExpandableListView的上一级视图有特定的大小的话,比如100像素,则可以使用wrap_content)
如果由于开发的时候粗心,对ExpandableListView指定wrap_content的值,则会报一个在SetContentView处的空指针错误。
根据描述,先看一个简单的例子:
定义XML的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ExpandableListView
android:id = "@+id/myExpandableListView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
/>
</LinearLayout>
就是在LinearLayout中布置了一个ExpandableListView。
在JAVA中的代码如下:
/*
* Android开发之ExpandableListView
* 北京Android俱乐部群:167839253
* Created on: 2012-7-23
* ExpandableListViewActivity.java
* Author: blueeagle
* Email: liujiaxiang@gmail.com
*/
package com.blueeagle.www;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.TextView;
public class ExpandableListViewActivity extends Activity {
private List<String> GroupData;//定义组数据
private List<List<String>> ChildrenData;//定义组中的子数据
private void LoadListDate() {
GroupData = new ArrayList<String>();
GroupData.add("国家");
GroupData.add("人物");
GroupData.add("武器");
ChildrenData = new ArrayList<List<String>>();
List<String> Child1 = new ArrayList<String>();
Child1.add("蜀国");
Child1.add("魏国");
Child1.add("吴国");
ChildrenData.add(Child1);
List<String> Child2 = new ArrayList<String>();
Child2.add("关羽");
Child2.add("张飞");
Child2.add("典韦");
Child2.add("吕布");
Child2.add("曹操");
Child2.add("甘宁");
Child2.add("郭嘉");
Child2.add("周瑜");
ChildrenData.add(Child2);
List<String> Child3 = new ArrayList<String>();
Child3.add("青龙偃月刀");
Child3.add("丈八蛇矛枪");
Child3.add("青钢剑");
Child3.add("麒麟弓");
Child3.add("银月枪");
ChildrenData.add(Child3);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LoadListDate();
ExpandableListView myExpandableListView = (ExpandableListView)findViewById(R.id.myExpandableListView);
myExpandableListView.setAdapter(new ExpandableAdapter());
}
private class ExpandableAdapter extends BaseExpandableListAdapter {
@Override
public Object getChild(int groupPosition, int childPosition) {
return ChildrenData.get(groupPosition).get(childPosition);
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return 0;
}
@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
TextView myText = null;
if (convertView != null) {
myText = (TextView)convertView;
myText.setText(ChildrenData.get(groupPosition).get(childPosition));
} else {
myText = createView(ChildrenData.get(groupPosition).get(childPosition));
}
return myText;
}
@Override
public int getChildrenCount(int groupPosition) {
return ChildrenData.get(groupPosition).size();
}
@Override
public Object getGroup(int groupPosition) {
return GroupData.get(groupPosition);
}
@Override
public int getGroupCount() {
return GroupData.size();
}
@Override
public long getGroupId(int groupPosition) {
return 0;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
TextView myText = null;
if (convertView != null) {
myText = (TextView)convertView;
myText.setText(GroupData.get(groupPosition));
} else {
myText = createView(GroupData.get(groupPosition));
}
return myText;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return false;
}
private TextView createView(String content) {
AbsListView.LayoutParams layoutParams = new AbsListView.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, 80);
TextView myText = new TextView(ExpandableListViewActivity.this);
myText.setLayoutParams(layoutParams);
myText.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
myText.setPadding(80, 0, 0, 0);
myText.setText(content);
return myText;
}
}
}
效果如下图所示:
这样就完成了一个最简单的ExpandableListView的控件。
在实际开发过程中,常常有不同的需求,比如每一个child需要不同的控件,每一个group或者child需要有图标,图标显示需要不一样,需要设置背景等各种各样能够让我们的程序变得美观的需求。那么下面就逐一讨论一下ExpandableListView如何实现这些需求。
比如,为ExpandableListView设置背景,并且默认展开第n组,n从0开始计数,则只需要添加如下代码:
myExpandableListView.setBackgroundResource(R.drawable.background);
myExpandableListView.expandGroup(0);
则效果如下:
改变每个组前面的图标,并且图标样式随着合拢和展开不同,则只需要在res/drawable目录下定义文件:Indicator.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_expanded="true" android:drawable="@drawable/right" />
<item android:drawable="@drawable/down"></item>
</selector>
在JAVA文件中添加:
myExpandableListView.setGroupIndicator(this.getResources().getDrawable(R.drawable.indicator));
效果如下:
对于其他的属性设置,可以参考以下属性说明:
android:childDivider
来分离子列表项的图片或者是颜色。注:图片不会完全显示,分离子列表项的是一条直线
android:childIndicator
在子列表项旁边显示的指示符。注:可以是一个图片
android:childIndicatorLeft
子列表项指示符的左边约束位置。注:即从左端0位置开始计数,比如,假设指示符是一个图标,给定这个属性值为3dip,则表示从左端起3dip开始显示此图标。
android:childIndicatorRight
子列表项指示符的右边约束位置。注:表示右端到什么位置结束
android:groupIndicator
在组列表项旁边显示的指示符。注:可以是一个图片。
android:indicatorLeft
组列表项指示器的左边约束位置。注:表示左端从什么位置开始。
android:indicatorRight
组列表项指示器的右边约束位置。注:表示右端到什么位置结束。
当然,还可以使用自定义的View去描述group和child,自定义的View可以和布局文件一样,写在layout文件夹下。例如命名为group.xml或者child.xml。
例如,我们定义一个child项由一个ImageView和一个TextView来组成,则可以定义child.xml为:<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation = "horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_gravity = "center_vertical"
android:id = "@+id/imageView01"
android:layout_width = "70px"
android:layout_height = "70px"
android:paddingLeft = "30px"
android:paddingTop = "2px"
android:paddingBottom = "5px"
android:src = "@drawable/ic_launcher"/>
<TextView
android:layout_gravity = "center_vertical"
android:id = "@+id/childTV"
android:layout_width = "match_parent"
android:layout_height = "match_parent"
android:paddingLeft = "30px"
android:paddingTop = "10px"
android:paddingBottom = "5px"
android:textSize = "30sp"/>
</LinearLayout>
对于其中的属性不再做详细说明。
对于ExpandableListView中的数据,还可以用以下方式定义:List<Map<String, String>> groups = new ArrayList<Map<String, String>>();
Map<String, String> group1 = new HashMap<String, String>();
group1.put("group", "国家");
… …
groups.add(group1);
… …
//准备第一个一级列表中的二级列表数据:三个二级列表,分别显示"魏国"、"蜀国"和"吴国"
List<Map<String, String>> child1 = new ArrayList<Map<String, String>>();
Map<String, String> child1Data1 = new HashMap<String, String>();
child1Data1.put("child", "魏国");
… …
child1.add(child1Data1);
… …
//准备第二个一级列表中的二级列表数据:八个二级列表,显示"关羽"、"张飞"、"典韦"、"吕布"、"曹操"、"甘宁"、"郭嘉"、"周瑜"
List<Map<String, String>> child2 = new ArrayList<Map<String, String>>();
Map<String, String> child2Data1 = new HashMap<String, String>();
child2Data1.put("child", "关羽");
… …
child2.add(child2Data1);
… …
//准备第三个一级列表中的二级列表数据:五个二级列表,显示 "青龙偃月刀"、"丈八蛇矛枪"、 "青钢剑"、"麒麟弓"、"银月枪"
List<Map<String, String>> child3 = new ArrayList<Map<String, String>>();
Map<String, String> child3Data1 = new HashMap<String, String>();
child3Data1.put("child", "青龙偃月刀");
… …
child3.add(child3Data1);
… …
//用一个list对象保存所有的二级列表数据
List<List<Map<String, String>>> childs = new ArrayList<List<Map<String, String>>>();
childs.add(child1);
childs.add(child2);
childs.add(child3);
针对上述数据定义方式,修改java文件:
例如:在getChildView函数中做如下编写,
String text = groups.get(groupPosition).get("group");
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//获取一级列表布局文件,设置相应元素属性
LinearLayout linearLayout = (LinearLayout) layoutInflater.inflate(R.layout.group, null);
TextView textView = (TextView)linearLayout.findViewById(R.id.textView01);
textView.setText(text);
return linearLayout;
这样就可以将自定义的View写入到child中,当然,这里也可以不用布局文件来定义View,也可以自己用代码实现View。
补充知识:
对于ExpandableListView相应的,也有一个ExpandableListActivity与之对应,对于只需要一个ExpandableListView的Activity,则只需要使用ExpandableListActivity来完成相应的功能就可以了。但是需要注意一点的是:在main.xml页面中添加如下代码:<ListView android:id="@android:id/list" 或android:id="@id/android:list"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</ListView>
这个ID不能随便修改,否则会出现异常:java.lang.RuntimeException:Your content must have a ExpandableListView whose id attribute is'android.R.id.list'.