转载请注明出处: http://blog.csdn.net/like_program/article/details/52549435
在做项目的时候,想在 ExpandableListView 中嵌套一个 GridView,在实现的过程中,遇到了不少坑,所以写篇博客记录一下,也顺便帮助下和我一样的新手。
我一直觉得,再多的文字,再多的代码片段,都不如写个小 Demo 更直观,所以在以后的博客中,我都尽量用小 Demo 来讲解,也给出源码。
先上一张最终效果图:
打开 Android Studio,我们新建一个 ExpandableListViewTest 项目,在项目中新建两个包(package), 分别命名为 activity 和 adapter,然后将 Android Studio 默认创建的 MainActivity 拖入 activity 包中。
为了让项目运行起来看上去更美观,我们用 Theme 主题,修改 AndroidManifest.xml 中的 application 下的 android:theme 属性,修改为@android:style/Theme
,
修改后的 AndroidManifest.xml 代码如下所示:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@android:style/Theme" >
<activity android:name=".activity.MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
修改了主题后,我们再修改下 MainActivity.Java 文件,MainActivity 默认继承自 AppCompatActivity ,我们把 AppCompatActivity 修改为 Activity,让 MainActivity 继承 Activity,这样等会项目才能正常运行。
我们先来写一下布局文件,既然我们想在 ExpandableListView 中嵌套一个 GridView,那么首先我们应该写一个 ExpandableListView
activity_main.xml 代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<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"
tools:context=".activity.MainActivity">
<ExpandableListView
android:id="@+id/expandableList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:childDivider="@drawable/separator"
android:divider="@drawable/separator"/>
</RelativeLayout>
布局文件非常简单,在布局中只放置了一个 ExpandableListView,并设置了各组之间的分割线和各组子项之间的分割线,分割线是一张图片。接下来我们就要在 MainActivity 中实例化 ExpandableListView 的布局,并准备好 ExpandableListView 要显示的数据,然后把数据传入 ExpandableListView 的适配器,这样才能把 ExpandableListView 显示出来。
MainActivity.java 代码如下所示:
package com.example.expandablelistviewtest.activity;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ExpandableListView;
import com.example.expandablelistviewtest.R;
import com.example.expandablelistviewtest.adapter.MyExpandableListViewAdapter;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private ExpandableListView expandableListView;
/**
* 每个分组的名字的集合
*/
private List<String> groupList;
/**
* 每个分组下的每个子项的 GridView 数据集合
*/
private List<String> itemGridList;
/**
* 所有分组的所有子项的 GridView 数据集合
*/
private List<List<String>> itemList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
expandableListView = (ExpandableListView) findViewById(R.id.expandableList);
groupList = new ArrayList<>();
groupList.add("分组1");
groupList.add("分组2");
itemGridList = new ArrayList<>();
for (int i = 0; i < 4; i++) {
itemGridList.add("电脑" + (i + 1));
}
itemList = new ArrayList<>();
itemList.add(itemGridList);
itemList.add(itemGridList);
MyExpandableListViewAdapter adapter = new MyExpandableListViewAdapter(MainActivity.this,
groupList, itemList);
expandableListView.setAdapter(adapter);
expandableListView.setGroupIndicator(null);
expandableListView.expandGroup(0);
}
}
- 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
- 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
既然我们要设置 ExpandableListView 适配器,那首先我们要定义一个 ExpandableListView 适配器,在 adapter 包下新建一个 MyExpandableListViewAdapter.java 类,在 ExpandableListView 适配器中,我们将把在 MainActivity 中准备好的数据传入这个适配器,并定义这些数据该怎么显示,因此我们还要创建一个 ExpandableListView 的分组布局文件 和 子项布局文件。不过不要着急,我们先定义好适配器。
MyExpandableListViewAdapter.java 代码如下所示:
package com.example.expandablelistviewtest.adapter;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseExpandableListAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.example.expandablelistviewtest.R;
import java.util.List;
/**
* ExpandableListView 适配器
*/
public class MyExpandableListViewAdapter extends BaseExpandableListAdapter {
private Context mContext;
/**
* 每个分组的名字的集合
*/
private List<String> groupList;
/**
* 所有分组的所有子项的 GridView 数据集合
*/
private List<List<String>> itemList;
private GridView gridView;
public MyExpandableListViewAdapter(Context context, List<String> groupList,
List<List<String>> itemList) {
mContext = context;
this.groupList = groupList;
this.itemList = itemList;
}
@Override
public int getGroupCount() {
return groupList.size();
}
@Override
public int getChildrenCount(int groupPosition) {
return itemList.get(groupPosition).size();
}
@Override
public Object getGroup(int groupPosition) {
return groupList.get(groupPosition);
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return itemList.get(groupPosition).get(childPosition);
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup
parent) {
if (null == convertView) {
convertView = View.inflate(mContext, R.layout.expandablelist_group, null);
}
ImageView ivGroup = (ImageView) convertView.findViewById(R.id.iv_group);
TextView tvGroup = (TextView) convertView.findViewById(R.id.tv_group);
if (isExpanded) {
ivGroup.setImageResource(R.drawable.ic_open);
} else {
ivGroup.setImageResource(R.drawable.ic_close);
}
tvGroup.setText(groupList.get(groupPosition));
return convertView;
}
@Override
public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View
convertView, ViewGroup parent) {
if (null == convertView) {
convertView = View.inflate(mContext, R.layout.expandablelist_item, null);
}
gridView = (GridView) convertView;
MyGridViewAdapter gridViewAdapter = new MyGridViewAdapter(mContext, itemList.get
(groupPosition));
gridView.setAdapter(gridViewAdapter);
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(mContext, "点击了第" + (groupPosition + 1) + "组,第" +
(position + 1) + "项", Toast.LENGTH_SHORT).show();
}
});
return convertView;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return false;
}
}
- 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
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 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
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
我们可以看到,ExpandableListView 的适配器代码看上去很多,但实际上并不复杂,因为大部分代码都是重写父类的方法,这里对 getChildView 的某些代码解释一下:convertView 的布局就是一个 GridView,但是实例化布局后,convertView 是 View 类型,所以 convertView 没有 setAdapter() 方法,需要向下转型为 GridView ,才能调用 setAdapter() 方法,设置适配器。
定义好了 ExpandableListView 的适配器,我们来写一下 ExpandableListView 的分组布局文件 和 子项布局文件。我们先写 ExpandableListView 的分组布局文件,在 layout 文件夹下,新建 expandablelist_group.xml ,代码如下所示:
<?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:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:src="@drawable/ic_open"/>
<TextView
android:id="@+id/tv_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="center"
android:text="分组"
android:textSize="18sp"/>
</LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
我们可以看到,分组布局非常简单,就是一个横向的线性布局,然后依次排列了一个 指示箭头 图片 和一个 分组 名字,我们继续写 ExpandableListView 的子项布局文件,在 layout 文件夹下,新建 expandablelist_item.xml ,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:numColumns="3">
</GridView>
我们可以看到,子项布局更简单,就是一个有 3 列数据的 GridView。
对了,刚才我们在 ExpandableListView 的适配器中创建了 GridView 的适配器,那我们赶紧去定义一下 GridView 的适配器吧。
在 adapter 包下,新建 MyGridViewAdapter.java ,代码如下所示:
package com.example.expandablelistviewtest.adapter;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.example.expandablelistviewtest.R;
import java.util.List;
/**
* GridView 适配器
*/
public class MyGridViewAdapter extends BaseAdapter {
private Context mContext;
/**
* 每个分组下的每个子项的 GridView 数据集合
*/
private List<String> itemGridList;
public MyGridViewAdapter(Context mContext, List<String> itemGridList) {
this.mContext = mContext;
this.itemGridList = itemGridList;
}
@Override
public int getCount() {
return itemGridList.size();
}
@Override
public Object getItem(int position) {
return itemGridList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (null == convertView) {
convertView = View.inflate(mContext, R.layout.gridview_item, null);
}
TextView tvGridView = (TextView) convertView.findViewById(R.id.tv_gridview);
tvGridView.setText(itemGridList.get(position));
return convertView;
}
}
- 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
- 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
GridView 的适配器和 ExpandableListView 的适配器一样,逻辑并不复杂,主要就是重写父类的方法,在 getView() 方法中,我们实例化了 GridView 的子项布局,所以,我们也要写一下 GridView 的子项布局文件,在 layout 文件夹中,新建 gridview_item.xml ,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_gridview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableTop="@drawable/icon_computer"
android:gravity="center"
android:text="电脑"/>
</LinearLayout>
GridView 子项布局只定义了一个带有图片的 TextView。
好了,我们来运行一下,看看效果。
好像已经成功了,不过貌似有些问题:
- 它的每个分组的每个子项里都有一个 GridView,但我们只想在每个分组里只显示一个 GridView 。
- 在点击 GridView 中的子项时,我们发现,在同一个分组里,点击不同子项里 GridView 的第一个子项时,都显示的第 N 组第 1 项。
- 我们定义的 GridView 有 4 个数据,分别是 电脑 1 ,电脑 2 ,电脑 3 ,电脑 4 。但是它只显示了 3 个。
我们逐一解决这些问题。
首先,我们可以发现,每组都有 4 行数据,也就是说,每组都有 4 个子项,那么如果我们想要在每个分组里只显示一个 GridView ,也就是一个子项,那么我们要先找到我们之前定义过的和 4 相关的数据,经过查找,我们发现,和 4 相关的数据只有一个,就是 itemGridList ,它里面有 4 个数据,找到了它之后,我们再找一下它在哪里被调用了,经过查找,我们发现,在 ExpandableListView 适配器中的 getChildrenCount() 方法中调用了它,有些同学了可能会疑惑,这里调用的是 itemList.get(groupPosition).size()
,关 itemGridList 什么事,其实我们之前在 MainActivity 中是这样创建 itemList 的:itemList.add(itemGridList)
,在这行代码中,itemGridList 被添加到了 itemList 中,那么在 getChildrenCount() 方法中,调用itemList.get(groupPosition)
,得到的就是 itemGridList ,再调用它的 size() 方法,得到的就是 itemGridList 的大小了,也就是 4,知道了这个,我们想在每个分组里只显示一个 GridView,也就好办了,在 getChildrenCount() 方法中,我们直接返回 1,即
@Override
public int getChildrenCount(int groupPosition) {
return 1;
}
再运行一下程序,我们发现,果然每个分组里只显示了一个 GridView 。
我们再来看第二个问题,在同一个分组里,点击不同子项里 GridView 的第一个子项时,都显示的第 N 组第 1 项。
我们查看下 ExpandableListView 适配器代码中的 getChildView() 代码中的设置子项监听器代码:
Toast.makeText(mContext, "点击了第" + (groupPosition + 1) + "组,第" +
(position + 1) + "项", Toast.LENGTH_SHORT).show()
这里使用了两个变量, groupPosition 很容易理解,就是 分组的 id ,position 可能有些同学会迷惑,position 并不是分组的子项 id ,而是 GridView 的子项 id ,弄清了这个,第二个问题也就不难懂了,我们不管点击的是分组下的哪一个子项中的 GridView 的第一项,都显示是 第一项,是因为 position 表示的 GridView 的子项 id ,和 分组下的子项 id 并没有关系。
我们再来看第三个问题,我们定义的 GridView 有 4 个数据,但是它只显示了 3 个。
首先,它只显示 3 个的部分原因是因为我们定义的 GridView 一行只能显示 3 个数据,我们可以查看 expandablelist_item.xml 的代码:android:numColumns="3"
更重要的原因是因为 GridView 的父容器测量模式为 UNSPECIFIED 的时候,GridView 的高度默认为一个 item 的高度,GridView 中源码如下:
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2
}
既然是这样的话,我们就重写 GridView 的 onMeasure 方法,自定义高度,在 activity 包中,新建 MyGridView.java ,代码如下:
package com.example.expandablelistviewtest.activity;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.GridView;
/**
* 自定义 GridView
*/
public class MyGridView extends GridView {
public MyGridView(Context context) {
super(context);
}
public MyGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyGridView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 重写该方法,达到使 GridView 适应 ExpandableListView 的效果
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
- 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
- 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
关于重写 GridView 的 onMeasure 方法的原理分析,可以查看这两篇文章。
android开发游记:ScrollView嵌套ListView,ListView完全展开及makeMeasureSpec测量机制原理分析
详解嵌套ListView、ScrollView布局显示不全的问题
写好了 MyGridView 后,在 MyGridView 上点击右键,选择 Copy Reference
,复制 MyGridView 的全路径名,替换 expandablelist_item.xml 中的 GridView 标签。
替换后的 expandablelist_item.xml 代码如下:
<?xml version="1.0" encoding="utf-8"?>
<com.example.expandablelistviewtest.activity.MyGridView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:numColumns="3">
</com.example.expandablelistviewtest.activity.MyGridView>
重新运行程序,我们发现,GridView 已经完美嵌套在 ExpandableListView 中了。
源码下载