一. 前言

1. 什么是ExpandableListView?

1
2
3
4
5
6
7
java.lang.Object
↳android.view.View
↳android.view.ViewGroup
↳android.widget.AdapterView<T extends android.widget.Adapter>
↳android.widget.AbsListView
↳android.widget.ListView
↳android.widget.ExpandableListView

根据继承图来看,ExpandableListView是ListView的子类。而expandable 在英文中的意思是可扩展的,所以ExpandableListView就是一个可以扩展的、有层级的ListView。

二. 属性 & 属性

1. 指示器

1
2
3
android:groupIndicator //group指示器,取值可以是任意的Drawable对象,默认情况下是一个向下的箭头,点击展开内容之后会变成向上的箭头

android:childIndicator //item指示器,取值可以是任意的Drawable对象,默认情况下什么都没有

① 如果想取消默认的 gropIndicator,可以将它赋值为”@null”,即:android:groupIndicator="@null"

② 替换默认indicator时,我们使用了一个selector,在编写这个selector时需要特别注意,我们用的状态是 state_expanded,这个状态在编写的时候不会自动补全,必须完全手动拼写!!

③ 默认情况下,指示器会覆盖组条目内容。为了避免这种情况,我们就需要手动的在布局文件中或者代码中将组条目向右移,以保证条目内容不被覆盖。

④ 例子

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/weixin_friend" android:state_expanded="true"/>
<item android:drawable="@drawable/xiangguan"/>
</selector>

2. 普通方法

1
2
3
4
5
6
7
8
9
10
11
public boolean collapseGroup(int groupPos)
收起 groupPos 位置的分组

public boolean expandGroup(int groupPos)
展开 groupPos 位置的分组

public boolean isGroupExpanded(int groupPosition)
判断 groupPosition 位置的分组是否展开

public void setAdapter(ExpandableListAdapter adapter)
给 ExpandableListView 设置适配器

3. 监听方法

1
2
3
4
5
6
7
8
9
10
11
public void setOnChildClickListener(OnChildClickListener onChildClickListener)
设置分组中子条目的点击监听器

public void setOnGroupClickListener(OnGroupClickListener listener)
设置分组的点击监听器

public void setOnGroupCollapseListener(OnGroupCollapseListener listener)
设置分组收起的监听器

public void setOnGroupExpandListener(OnGroupExpandListener listener)
设置分组展开的监听器

4. 其他的属性

1
2
3
4
5
6
7
8
9
10
divider        //group之间的分割线样式
dividerHeight //分割线的高度

childDivider //item之间分割样式

indicatorLeft //箭头或者自己设置的图片的左边框距离手机左边边缘的距离,类似于marginLeft
indicatorStart //箭头或者自己设置的图片的左边框距离手机左边边缘的距离,类似于marginStart

indicatorRight //暂时还用不上,因为箭头一般在列表的左边,使用方法和上面类似
indicatorEnd //暂时还用不上,因为箭头一般在列表的左边,使用方法和上面类似

三. BaseExpandableListAdapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class MyAdapter extends BaseExpandableListAdapter {


@Override
public int getGroupCount() {
//group的数量
}

@Override
public int getChildrenCount(int groupPosition) {
//某个group的item的数量
}

@Override
public Object getGroup(int groupPosition) {
//某个group的数据
}

@Override
public Object getChild(int groupPosition, int childPosition) {
//某个group的某个item的数据
}

@Override
public long getGroupId(int groupPosition) {
//group的id
}

@Override
public long getChildId(int groupPosition, int childPosition) {
//item的id
}

@Override
public boolean hasStableIds() {
//刷新相关
}

@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
//获取group的View
}

@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
//获取item的View
}

@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
//子项是否可选中
}
}

关于hasStableIds的作用

参考:hasStableIds的作用

四. 实际例子

1. 自定义的ExpandableListView包括内部类的适配器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
public class XLExpandableListView extends ExpandableListView {

/**
* 构造方法:Java代码初始化
* @param context
*/
public XLExpandableListView(Context context) {
super(context);

//初始化操作
init();
}

/**
* 构造方法:Xml代码初始化
* @param context
* @param attrs
*/
public XLExpandableListView(Context context, AttributeSet attrs) {
super(context, attrs);

//初始化操作
init();
}

/**
* 初始化操作
*/
private void init() {
MyAdapter adapter = new MyAdapter();
setAdapter(adapter);
}

/**
* 自定义的适配器
*/
class MyAdapter extends BaseExpandableListAdapter {

//group的数量
@Override
public int getGroupCount() {
return DataUtil.getGroups().size();
}

//某个group的item的数量
@Override
public int getChildrenCount(int groupPosition) {
return DataUtil.getItems().get(groupPosition).size();
}

//某个group的数据
@Override
public Object getGroup(int groupPosition) {
return DataUtil.getGroups().get(groupPosition);
}

//某个group的某个item的数据
@Override
public Object getChild(int groupPosition, int childPosition) {
return DataUtil.getItems().get(groupPosition).get(childPosition);
}

//group的id
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}

//item的id
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}

//刷新相关
@Override
public boolean hasStableIds() {
return false;
}

//获取group的View
@SuppressLint("InflateParams")
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
if (convertView == null){
convertView = LayoutInflater.from(getContext()).inflate(R.layout.group_layout,null);
}

TextView group_name_view = convertView.findViewById(R.id.group_name);
group_name_view.setText((String) getGroup(groupPosition));

return convertView;
}

//获取item的View
@SuppressLint("InflateParams")
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
if (convertView == null){
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_layout,null);
}

TextView item_name_view = convertView.findViewById(R.id.item_name);
item_name_view.setText((String) getChild(groupPosition,childPosition));

return convertView;
}

//子项是否可选中
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
}
}

2. group和item的布局

① group的布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:background="#22666666">

<TextView
android:id="@+id/group_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="组名"
android:textSize="18sp"
android:layout_marginStart="50dp"
android:layout_centerVertical="true"
/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="4/18"
android:textSize="16sp"
android:layout_alignParentEnd="true"
android:layout_marginEnd="10dp"
android:layout_centerVertical="true"
/>


</RelativeLayout>

② item的布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingTop="10dp"
android:paddingBottom="10dp"
>

<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@mipmap/ic_launcher_round"
android:scaleType="fitXY"
android:layout_marginStart="40dp"
/>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginStart="12dp"
>

<TextView
android:id="@+id/item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="好友名"
android:textStyle="bold"
android:textSize="16sp"
/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="凡事都有偶然的凑巧"
android:textSize="14sp"
/>

</LinearLayout>


</LinearLayout>

3. 数据源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class DataUtil {
/**
* 获取组数据
* @return
*/
public static List<String> getGroups(){
//group数据
List<String> groups = new ArrayList<>();

//加入数据
groups.add("我的家人");
groups.add("我的朋友");
groups.add("黑名单");

return groups;
}

/**
* 获取每一项数据
* @return
*/
public static List<List<String>> getItems(){
//item数据
List<List<String>> items = new ArrayList<>();

//加入数据
List<String> group_1_item = new ArrayList<>();
group_1_item.add("爸爸");
group_1_item.add("妈妈");
group_1_item.add("哥哥");
items.add(group_1_item);
List<String> group_2_item = new ArrayList<>();
group_2_item.add("张三");
group_2_item.add("李四");
group_2_item.add("王二麻");
items.add(group_2_item);
List<String> group_3_item = new ArrayList<>();
group_3_item.add("卖茶叶");
group_3_item.add("微商");
group_3_item.add("盗号");
items.add(group_3_item);

return items;
}
}

4. 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<swu.xl.expandablelistview.XLExpandableListView
android:id="@+id/expand_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:groupIndicator="@drawable/indicator"
/>

</RelativeLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

XLExpandableListView listView = findViewById(R.id.expand_list_view);

//group的点击监听
listView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
Toast.makeText(MainActivity.this, DataUtil.getGroups().get(groupPosition)+"被点击了", Toast.LENGTH_SHORT).show();

return false;
}
});

//item的点击监听
listView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
Toast.makeText(MainActivity.this, DataUtil.getItems().get(groupPosition).get(childPosition)+"被点击了", Toast.LENGTH_SHORT).show();
return true;
}
});

}
}

5. Github地址

ExpandableListView

6. 运行效果

五. 问题解决

1. 去除默认点击水波纹效果

1
2
<!-- styles中添加 -->
<item name="android:colorControlHighlight">@android:color/transparent</item>

六. 参考文章

ExpandableListView

安卓的ExpandableListView的使用和优化

ExpandableListView–基本使用介绍