一.ExpandableListView的基础知识
和ListView不同的是它是一个两级的滚动列表视图,每一个组可以展开,显示一些子项,类似于 QQ列表,这些项目来至于ExpandableListAdapter的子类,也就是说,要实现向里面添加项目,必须写一个子类实现ExpandableListAdapter的接口或者使用系统为我们实现在子类。
(一)常用属性:
- android:childDivider 指定各组内子类表项之间的分隔条,
- android:childIndicator 显示在子列表旁边的Drawable对象
- android:childIndicatorLeft 子列表项指示符的左边约束位置
- android:childIndicatorRight 子列表项指示符的右边约束位置
- android:groupIndicator 显示在组列表旁边的Drawable对象
- android:indicatorLeft 组列表项指示器的左边约束位置
- android:indicatorRight 组列表项指示器的右边约束位置
(二)适配器
一般适用于ExpandableListView的Adapter都要继承BaseExpandableListAdapter这个类, 并且必须重载getGroupView和getChildView这两个最为重要的方法。
当扩展BaseExpandableListAdapter时,关键是实现如下四个抽象方法:
方法1:
//取得显示给定分组给定子位置的数据用的视图。
public abstract View getChildView (int groupPosition, intchildPosition , boolean isLastChild, ViewconvertView, ViewGroup parent){
//groupPosition 包含要取得子视图的分组位置。
//childPosition 分组中子视图(要返回的视图)的位置。
//isLastChild 该视图是否为组中的最后一个视图。
//convertView 如果可能,重用旧的视图对象,使用前你应该保证视图对象为非 空,并且是否是合适的类型,如果该对象不能转换为可以正确显示数据的视图,该方法就创建新 视图.不保证使用先前由getChildView(int, int,boolean, View, ViewGroup)创建的 视图.
//parent 该视图最终从属的父视图.
返回 指定位置相应的子视图.
}方法2:
//取得指定分组的子元素数。
public abstract int getChildrenCount (int groupPosition){
//groupPosition 要取得子元素个数的分组位置。
返回 指定分组的子元素个数.
}方法3:
//取得用于显示给定分组的视图.这个方法仅返回分组的视图对象,要想获取子元素的视图对象 ,就需要调用getChildView(int, int, boolean, View, ViewGroup)。
public abstract View getGroupView (int groupPosition, booleanisExpanded, View convertView, ViewGroupparent){
//groupPosition 决定返回哪个视图的组位置.
//isExpanded 该组是展开状态还是收起状态
//convertView 如果可能,重用旧的视图对象.使用前你应该保证视图对象为非空, 并且是否是合适的类型.如果该对象不能转换为可以正确显示数据的视图,该方法就创建新视图. 不保证使用先前由getGroupView(int, boolean,View, ViewGroup)创建的视图.
//parent 该视图最终从属的父视图.
返回 指定位置相应的组视图
}方法4:
//取得分组数
public abstract int getGroupCount (){
返回 分组数
}
注意:在XML布局文件中,如果ExpandableListView上一级视图的大小没有严格定义的 话,则不能对ExpandableListView的android:layout_height属性使用wrap_content 值。(例如,如果上一级视图是ScrollView的话,则不应该指定wrap_content的值,因为它可以是任意的长度。不过,如果ExpandableListView的上一级视图有特定的大小的 话,比如100像素,则可以使用wrap_content)。
二.分组列表示例的设计
程序运行后的界面:
点击一级条目后显示二级条目的界面:
程序中图像的显示是圆角的形式的,需要用到自定义类,程序设计代码如下:
(一)主布局页面activity_main.xml的设计
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ExpandableListView
android:dividerHeight="2dp"
android:divider="@null"
android:id="@+id/main_elv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:groupIndicator="@null" />
</RelativeLayout>
(二)一级目录布局文件item_group.xml的设计
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/shape"
android:padding="20dp" >
<TextView
android:id="@+id/groupName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="groutName"
android:textSize="20sp" />
<TextView
android:id="@+id/onlinenum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:text="0 / 8" />
</RelativeLayout>
(三)二级目录的布局item_user.xml文件的设计
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:padding="10dp" >
//圆角自定义View的使用:包名+类名
<com.example.lesson7_expandablelistview.CircleImageView
android:id="@+id/item_iv"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/netName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/item_iv"
android:paddingBottom="5dp"
android:text="netName" />
<TextView
android:id="@+id/isonline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/netName"
android:layout_toRightOf="@id/item_iv"
android:text="[在线]" />
<TextView
android:id="@+id/sign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/netName"
android:layout_toRightOf="@id/isonline"
android:text="sign" />
</RelativeLayout>
(四)圆角显示图片的自定义View的设计
这里不需要它深入理解里面的代码,直接复制使用就可以了
package com.example.lesson7_expandablelistview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
*圆角显示图片的自定义View
*
*/
public class CircleImageView extends ImageView {
private static final Xfermode MASK_XFERMODE;
private Bitmap mask;
private Paint paint;
private int mBorderWidth = 10;
private int mBorderColor = Color.parseColor("#f2f2f2");
private boolean useDefaultStyle = false;
static {
PorterDuff.Mode localMode = PorterDuff.Mode.DST_IN;
MASK_XFERMODE = new PorterDuffXfermode(localMode);
}
public CircleImageView(Context context) {
super(context);
}
public CircleImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CircularImage);
mBorderColor = a.getColor(R.styleable.CircularImage_border_color,
mBorderColor);
final int def = (int) (2 * context.getResources().getDisplayMetrics().density + 0.5f);
mBorderWidth = a.getDimensionPixelOffset(
R.styleable.CircularImage_border_width, def);
a.recycle();
}
void useDefaultStyle(boolean useDefaultStyle) {
this.useDefaultStyle = useDefaultStyle;
}
@Override
protected void onDraw(Canvas canvas) {
if (useDefaultStyle) {
super.onDraw(canvas);
return;
}
final Drawable localDraw = getDrawable();
if (localDraw == null) {
return;
}
if (localDraw instanceof NinePatchDrawable) {
return;
}
if (this.paint == null) {
final Paint localPaint = new Paint();
localPaint.setFilterBitmap(false);
localPaint.setAntiAlias(true);
localPaint.setXfermode(MASK_XFERMODE);
this.paint = localPaint;
}
final int width = getWidth();
final int height = getHeight();
int layer = canvas.saveLayer(0.0F, 0.0F, width, height, null, 31);
localDraw.setBounds(0, 0, width, height);
localDraw.draw(canvas);
if ((this.mask == null) || (this.mask.isRecycled())) {
this.mask = createOvalBitmap(width, height);
}
canvas.drawBitmap(this.mask, 0.0F, 0.0F, this.paint);
canvas.restoreToCount(layer);
drawBorder(canvas, width, height);
}
private void drawBorder(Canvas canvas, final int width, final int height) {
if (mBorderWidth == 0) {
return;
}
final Paint mBorderPaint = new Paint();
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setAntiAlias(true);
mBorderPaint.setColor(mBorderColor);
mBorderPaint.setStrokeWidth(mBorderWidth);
canvas.drawCircle(width / 2, height / 2, (width - mBorderWidth) / 2,
mBorderPaint);
canvas = null;
}
public Bitmap createOvalBitmap(final int width, final int height) {
Bitmap.Config localConfig = Bitmap.Config.ARGB_8888;
Bitmap localBitmap = Bitmap.createBitmap(width, height, localConfig);
Canvas localCanvas = new Canvas(localBitmap);
Paint localPaint = new Paint();
final int padding = (mBorderWidth - 3) > 0 ? mBorderWidth - 3 : 1;
RectF localRectF = new RectF(padding, padding, width - padding, height
- padding);
localCanvas.drawOval(localRectF, localPaint);
return localBitmap;
}
}
(五)圆角自定义View的资源文件:values中circle_attr.xml文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircularImage">
<attr name="border_width" format="dimension" />
<attr name="border_color" format="color" />
</declare-styleable>
</resources>
(六)一级目录的数据原型的类
package com.example.lesson7_expandablelistview;
import java.util.ArrayList;
import java.util.List;
/*
* 设置一级条目中显示的信息,但是里面包含二级条目 的数据
* */
public class Group {
// 条目包含的内容
// 组名名字
String groupName;
// 组里面包含的人的信息,以对象的形式存放在集合中
List<User> usersList=new ArrayList<User>();
// 该组的总人数
public int getNumOfGroup() {
return usersList.size();
}
// 获取对应组中在线的人数
public int getOnlineUserOfGrout() {
// 获取在线的人的数量
int onLineNum = 0;
// 判断groupList里面的每个人的在线状态,在线就数量加1
for (User user : usersList) {
if (user.isOnline) {
onLineNum++;
}
}
// 返回的是在线的人数
return onLineNum;
}
// 添加组内的用户
public void addUserToGroup(User user) {
usersList.add(user);
}
}
(七)二级条目的数据原型的类的设计
package com.example.lesson7_expandablelistview;
/*
* 二级条目数据
* 每一个用户显示的基本信息
* */
public class User {
// 用户的基本信息
int imageId;
String net_Name;
boolean isOnline;
String sign;
// 重写用户的构造方法,方便数据的写入
public User(int imageId, String net_Name, boolean isOnline, String sign) {
super();
this.imageId = imageId;
this.net_Name = net_Name;
this.isOnline = isOnline;
this.sign = sign;
}
// get和set方法
public int getImageId() {
return imageId;
}
public void setImageId(int imageId) {
this.imageId = imageId;
}
public String getNet_Name() {
return net_Name;
}
public void setNet_Name(String net_Name) {
this.net_Name = net_Name;
}
public boolean isOnline() {
return isOnline;
}
public void setOnline(boolean isOnline) {
this.isOnline = isOnline;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
}
(八)ExpandableListView的适配器
package com.example.lesson7_expandablelistview;
import java.util.List;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class MyExpandableListViewAdapter extends BaseExpandableListAdapter {
// 所有组的数据,每一组的数组里面又包含多个User信息
private List<Group> groupList;
Context context;
// 通过构造方法传入上下文
public MyExpandableListViewAdapter(Context content, List<Group> groupList) {
this.context = content;
this.groupList = groupList;
}
// 显示的组的数量
@Override
public int getGroupCount() {
// 返回的是页面显示多少个组的信息
return groupList.size();
}
// 显示的是组里面的用户的个数,这里要根据组的变化而变化
// 参数就是所在组的游标值
@Override
public int getChildrenCount(int groupPosition) {
// 先获得集合中对应的对象,再通过对象的方法获取里面的组的人数
return groupList.get(groupPosition).getNumOfGroup();
}
// 获取对应组的对象
@Override
public Group getGroup(int groupPosition) {
// 通过组的集合的序列值就可以拿到
return groupList.get(groupPosition);
}
// 获取对应组里面的对应用户(里面包含用户的信息)
// 第一个参数组的游标值,
// 第二个参数数用户在该组的游标值
@Override
public User getChild(int groupPosition, int childPosition) {
// 先找到该组,然后通过该组的用户列表集合的对应序列,来获取用户的信息
return groupList.get(groupPosition).usersList.get(childPosition);
}
// 组的序列号
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
// 用户的序列号
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
// 是否是稳定的ID,这里一般选择的是true
@Override
public boolean hasStableIds() {
return true;
}
// 用户条目是否可点击,这里一般也是选择返回true
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
// 下面两个方法是最麻烦和最重要的方法
// 返回的是组的视图界面
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
GroupHolder holder;
// 第一个加载条目时
if (convertView == null) {
// 加载组的布局页面
convertView = View.inflate(context, R.layout.item_group, null);
// 把加载的布局添加到holder里面
holder = new GroupHolder(convertView);
// 在缓冲的convertView对象中添加一个holder对象的标签
convertView.setTag(holder);
// 之后每一个对应的convertView都会有一个holder对象的值
} else {
// 如果不是第一次来加载条目,就用以前缓冲的数据
holder = (GroupHolder) convertView.getTag();// holder里面已经包含了布局的信息
Group group = getGroup(groupPosition);// 获取组的条目的对象
// 对组中的布局显示Group对象的文本数据
holder.groupName.setText(group.groupName);
holder.onlineNum.setText(group.getOnlineUserOfGrout() + " / "
+ group.getNumOfGroup());
}
// 返回的数具有View数据的缓冲数据
return convertView;
}
// 返回的是每一个用户的视图界面
@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
ChildHolder holder;
// 第一个加载条目时
if (convertView == null) {
// 加载组的布局页面
convertView = View.inflate(context, R.layout.item_user, null);
// 把加载的布局添加到holder里面
holder = new ChildHolder(convertView);
// 在缓冲的convertView对象中添加一个holder对象的标签
convertView.setTag(holder);
// 之后每一个对应的convertView都会有一个holder对象的值
} else {
// 如果不是第一次来加载条目,就用以前缓冲的数据
holder = (ChildHolder) convertView.getTag();// holder里面已经包含了布局的信息
User user = getChild(groupPosition, childPosition);// 获取用户的条目的对象
// 对用户的布局显示User对象的文本数据
holder.iv.setImageResource(user.imageId);
holder.netName.setText(user.net_Name);
holder.isonline.setText(user.isOnline ? "[在线]" : "[离线]");
holder.sign.setText(user.sign);
}
// 返回的数具有View数据的缓冲数据
return convertView;
}
// 这里还是创建两个ViewHolder来方便进行缓冲数据
class GroupHolder {
TextView groupName;
TextView onlineNum;
public GroupHolder(View convertView) {
groupName = (TextView) convertView.findViewById(R.id.groupName);
onlineNum = (TextView) convertView.findViewById(R.id.onlinenum);
}
}
class ChildHolder {
ImageView iv;
TextView netName;
TextView isonline;
TextView sign;
public ChildHolder(View convertView) {
iv = (ImageView) convertView.findViewById(R.id.item_iv);
netName = (TextView) convertView.findViewById(R.id.netName);
isonline = (TextView) convertView.findViewById(R.id.isonline);
sign = (TextView) convertView.findViewById(R.id.sign);
}
}
}
(九)主方法类的设计
package com.example.lesson7_expandablelistview;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ExpandableListView;
public class MainActivity extends Activity {
// 组的对象集合
List<Group> groupList;
// 布局控件
ExpandableListView elv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();// 初始化数据
}
// 初始化数据
private void initView() {
elv = (ExpandableListView) findViewById(R.id.main_elv);
// 添加数据源
// 数据源
// 图片1
int[] images = { R.drawable.a1, R.drawable.a2, R.drawable.a3,
R.drawable.a4, R.drawable.a5, R.drawable.a6, R.drawable.a7,
R.drawable.a8, R.drawable.a9 };
// 姓名
String[] names = { "唐嫣", "杨幂", "赵丽颖", "王子文", "张佳宁", "江铠同", "杨紫", "唐艺昕",
"关晓彤" };
// 文本内容
String[] sings = {
"1983年12月6日出生于上海。2006年毕业于中央戏剧学院表演系本科班。",
"1986年9月12日出生于北京市,中国内地影视女演员、流行乐歌手、影视制片人。",
"1987年10月16日出生于河北省廊坊市,中国内地影视女演员。2006年,因获得雅虎搜星比赛冯小刚组冠军而进入演艺圈;",
"1987年2月28日出生于四川成都,中国内地影视女演员、模特,毕业于中央戏剧学院。",
"1989年出生于吉林省辽源市,中国大陆女演员,2009年毕业于中央戏剧学院表演系本科。",
"1989年10月24日出生于山东省青岛市,毕业于中央戏剧学院,中国内地女演员。",
"1992年11月6日出生于北京市,2014年毕业于北京电影学院2010级表演系本科班。2002年,出演古装历史情感剧《孝庄秘史》开始崭露头角;",
"出生于四川省,现居住地重庆 ,中国内地女演员。2010年毕业于重庆大学美视电影学院表演系。2011年,唐艺昕参与郑晓龙导演执导的《后宫·甄嬛传》,开启了演艺生涯。 ",
"1997年9月17日出生于北京,学生、演员。2016年4月14日,北影艺考成绩出炉,关晓彤专业排名第一。" };
boolean[] isonlines = { true, false, true, false, true, true, false,
true, true };
User[] user = new User[names.length];
// 实例化User对象
for (int i = 0; i < names.length; i++) {
user[i] = new User(images[i], names[i], isonlines[i], sings[i]);
}
// 这里九个用户,设置第一组3人,第二组2人,第三组4人
// 实例化组对象存放的集合
groupList = new ArrayList<Group>();
// 一组
Group group1 = new Group();
group1.addUserToGroup(user[0]);
group1.addUserToGroup(user[1]);
group1.addUserToGroup(user[2]);
group1.groupName = "小学同学";
// 二组
Group group2 = new Group();
group2.addUserToGroup(user[3]);
group2.addUserToGroup(user[4]);
group2.groupName = "初中同学";
// 三组
Group group3 = new Group();
group3.addUserToGroup(user[5]);
group3.addUserToGroup(user[6]);
group3.addUserToGroup(user[7]);
group3.addUserToGroup(user[8]);
group3.groupName = "大学同学";
// 把一组添加到组的集合中
groupList.add(group1);
// 把二组添加到组的集合中
groupList.add(group2);
// 把三组添加到组的集合中
groupList.add(group3);
// 创建自定义好的适配器
MyExpandableListViewAdapter adapter = new MyExpandableListViewAdapter(this, groupList);
// 添加适配器到固定的布局中
elv.setAdapter(adapter);
}
}
上面就是ExpandableListView显示的使用示例。