Android ExpandableListView 中完美嵌套 GridView
2016-09-15 12:52
519 查看
转载请注明出处: 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 属性,修改为
修改后的 AndroidManifest.xml 代码如下所示:
修改了主题后,我们再修改下 MainActivity.java 文件,MainActivity 默认继承自 AppCompatActivity ,我们把 AppCompatActivity 修改为 Activity,让 MainActivity 继承 Activity,这样等会项目才能正常运行。
我们先来写一下布局文件,既然我们想在 ExpandableListView 中嵌套一个 GridView,那么首先我们应该写一个 ExpandableListView
activity_main.xml 代码如下所示:
布局文件非常简单,在布局中只放置了一个 ExpandableListView,并设置了各组之间的分割线和各组子项之间的分割线,分割线是一张图片。接下来我们就要在 MainActivity 中实例化 ExpandableListView 的布局,并准备好 ExpandableListView 要显示的数据,然后把数据传入 ExpandableListView 的适配器,这样才能把 ExpandableListView 显示出来。
MainActivity.java 代码如下所示:
既然我们要设置 ExpandableListView 适配器,那首先我们要定义一个 ExpandableListView 适配器,在 adapter 包下新建一个 MyExpandableListViewAdapter.java 类,在 ExpandableListView 适配器中,我们将把在 MainActivity 中准备好的数据传入这个适配器,并定义这些数据该怎么显示,因此我们还要创建一个 ExpandableListView 的分组布局文件 和 子项布局文件。不过不要着急,我们先定义好适配器。
MyExpandableListViewAdapter.java 代码如下所示:
我们可以看到,ExpandableListView 的适配器代码看上去很多,但实际上并不复杂,因为大部分代码都是重写父类的方法,这里对 getChildView 的某些代码解释一下:convertView 的布局就是一个 GridView,但是实例化布局后,convertView 是 View 类型,所以 convertView 没有 setAdapter() 方法,需要向下转型为 GridView ,才能调用 setAdapter() 方法,设置适配器。
定义好了 ExpandableListView 的适配器,我们来写一下 ExpandableListView 的分组布局文件 和 子项布局文件。我们先写 ExpandableListView 的分组布局文件,在 layout 文件夹下,新建 expandablelist_group.xml ,代码如下所示:
我们可以看到,分组布局非常简单,就是一个横向的线性布局,然后依次排列了一个 指示箭头 图片 和一个 分组 名字,我们继续写 ExpandableListView 的子项布局文件,在 layout 文件夹下,新建 expandablelist_item.xml ,代码如下所示:
我们可以看到,子项布局更简单,就是一个有 3 列数据的 GridView。
对了,刚才我们在 ExpandableListView 的适配器中创建了 GridView 的适配器,那我们赶紧去定义一下 GridView 的适配器吧。
在 adapter 包下,新建 MyGridViewAdapter.java ,代码如下所示:
GridView 的适配器和 ExpandableListView 的适配器一样,逻辑并不复杂,主要就是重写父类的方法,在 getView() 方法中,我们实例化了 GridView 的子项布局,所以,我们也要写一下 GridView 的子项布局文件,在 layout 文件夹中,新建 gridview_item.xml ,代码如下所示:
GridView 子项布局只定义了一个带有图片的 TextView。
好了,我们来运行一下,看看效果。
好像已经成功了,不过貌似有些问题:
它的每个分组的每个子项里都有一个 GridView,但我们只想在每个分组里只显示一个 GridView 。
在点击 GridView 中的子项时,我们发现,在同一个分组里,点击不同子项里 GridView 的第一个子项时,都显示的第 N 组第 1 项。
我们定义的 GridView 有 4 个数据,分别是 电脑 1 ,电脑 2 ,电脑 3 ,电脑 4 。但是它只显示了 3 个。
我们逐一解决这些问题。
首先,我们可以发现,每组都有 4 行数据,也就是说,每组都有 4 个子项,那么如果我们想要在每个分组里只显示一个 GridView ,也就是一个子项,那么我们要先找到我们之前定义过的和 4 相关的数据,经过查找,我们发现,和 4 相关的数据只有一个,就是 itemGridList ,它里面有 4 个数据,找到了它之后,我们再找一下它在哪里被调用了,经过查找,我们发现,在 ExpandableListView 适配器中的 getChildrenCount() 方法中调用了它,有些同学了可能会疑惑,这里调用的是
再运行一下程序,我们发现,果然每个分组里只显示了一个 GridView 。
我们再来看第二个问题,在同一个分组里,点击不同子项里 GridView 的第一个子项时,都显示的第 N 组第 1 项。
我们查看下 ExpandableListView 适配器代码中的 getChildView() 代码中的设置子项监听器代码:
这里使用了两个变量, groupPosition 很容易理解,就是 分组的 id ,position 可能有些同学会迷惑,position 并不是分组的子项 id ,而是 GridView 的子项 id ,弄清了这个,第二个问题也就不难懂了,我们不管点击的是分组下的哪一个子项中的 GridView 的第一项,都显示是 第一项,是因为 position 表示的 GridView 的子项 id ,和 分组下的子项 id 并没有关系。
我们再来看第三个问题,我们定义的 GridView 有 4 个数据,但是它只显示了 3 个。
首先,它只显示 3 个的部分原因是因为我们定义的 GridView 一行只能显示 3 个数据,我们可以查看 expandablelist_item.xml 的代码:
更重要的原因是因为 GridView 的父容器测量模式为 UNSPECIFIED 的时候,GridView 的高度默认为一个 item 的高度,GridView 中源码如下:
既然是这样的话,我们就重写 GridView 的 onMeasure 方法,自定义高度,在 activity 包中,新建 MyGridView.java ,代码如下:
关于重写 GridView 的 onMeasure 方法的原理分析,可以查看这两篇文章。
android开发游记:ScrollView嵌套ListView,ListView完全展开及makeMeasureSpec测量机制原理分析
详解嵌套ListView、ScrollView布局显示不全的问题
写好了 MyGridView 后,在 MyGridView 上点击右键,选择
替换后的 expandablelist_item.xml 代码如下:
重新运行程序,我们发现,GridView 已经完美嵌套在 ExpandableListView 中了。
源码下载
在做项目的时候,想在 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"); // 每个分组下的每个子项的 GridView 数据集合 itemGridList = new ArrayList<>(); for (int i = 0; i < 4; i++) { itemGridList.add("电脑" + (i + 1)); } // 所有分组的所有子项的 GridView 数据集合 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); } }
既然我们要设置 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); } // 因为 convertView 的布局就是一个 GridView, // 所以可以向下转型为 GridView gridView = (GridView) convertView; // 创建 GridView 适配器 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; } }
我们可以看到,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>
我们可以看到,分组布局非常简单,就是一个横向的线性布局,然后依次排列了一个 指示箭头 图片 和一个 分组 名字,我们继续写 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; } }
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); } }
关于重写 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 中了。
源码下载
相关文章推荐
- Android ExpandableListView 中完美嵌套 GridView
- Android ExpandableListView 中完美嵌套 GridView
- android中ExpandableListView嵌套GridView使用
- android 关于 ScrollView嵌套GridView,ListView 显示和滑动的问题
- android 关于 ScrollView嵌套GridView,ListView 显示和滑动的问题
- expandablelistview学习--在listView里面嵌套GridView
- android listView嵌套gridview的使用心得
- 完美解决Android中,ScrollView嵌套ListView的冲突。
- expandablelistview学习--在listView里面嵌套GridView
- Android ExpandableListView嵌套ListView
- android listView嵌套gridview的使用心得
- android ScrollView中嵌套GridView,ListView只显示一行的解决办法
- android listView嵌套gridview的使用心得
- 完美解决android:ScrollView嵌套ListView的问题 .
- android关于scrollview嵌套ExpandableListView的实现1
- expandablelistview学习--在listView里面嵌套GridView
- expandablelistview学习--在listView里面嵌套GridView
- [android] ScrollView 嵌套 ListView GridView问题
- expandablelistview学习--在listView里面嵌套GridView
- android 在ListView中嵌套GridView要注意的