最近朋友公司的一个项目中用到了一ListView的Item嵌套ListView的功能,朋友问我这种效果该怎么实现。ListView嵌套ListView的情况在实际开发中用到的还是比较多的。谷歌也给了我们一个这样的控件叫ExpandableListView,它可以实现item的展开效果。ExpandableListView除了比ListView多了几个方法外用法几乎和ListView用法一样,只要ListView用的溜,ExpandableListView很快就能上手。
下面将结合给朋友写的一个Demo来讲解ExpandableListView的用法,在使用ExpandableListView时候也踩到了不少坑,接下来都会作详细说明。
先来看用ExpandableListView实现的效果图吧(注意:demo中用到的数据都是假数据,项目中虽然定义了GroupBean和ChildBean,也将这两个Bean的List集合传递给了Adapter,但是实际并没有用到)

布局文件中的代码,添加一个ExpandableListView

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/activity_main"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#EFEFEF"><ExpandableListViewandroid:id="@+id/lv_main"android:layout_width="match_parent"android:layout_height="match_parent"android:divider="#EFEFEF"android:layout_marginLeft="10dp"android:layout_marginRight="10dp"android:dividerHeight="8dp"/></RelativeLayout>

MainActivity中只要获取到ExpandableListView,并为ExpandableListView设置Adapter即可,用法跟ListView一模一样的。代码如下:

mExpandableListView = (ExpandableListView) findViewById(R.id.lv_main);MyAdapter adapter = new MyAdapter(this, mGroupBeanList, mLists);mExpandableListView.setAdapter(adapter);//  隐藏自带的三角mExpandableListView.setGroupIndicator(null);Drawable drawable = getDrawable(R.drawable.shape5);mExpandableListView.setChildDivider(drawable);

上边代码作下说明:mExpandableListView.setGroupIndicator(null)是隐藏默认的指示图标,由于项目需要,这个Demo中的指示图标是自定义的,所以隐藏了默认的。盗张图,感受下默认的图标样式:

如果需要让指示图标显示在右边也是可以的,只需要添加下边两行代码即可:

int width = getWindowManager().getDefaultDisplay().getWidth();
mExpandableListView.setIndicatorBounds(width-50, width);

接下来看下GroupItem和ChildItem的布局文件
GroupItem布局效果图和代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/rl_group_item"android:layout_width="match_parent"android:layout_height="105dp"android:background="@drawable/shape"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="105dp"><ImageViewandroid:id="@+id/iv_group_item"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="8dp"android:layout_marginRight="4dp"android:layout_marginTop="12dp"android:background="@drawable/mine_card"/><TextViewandroid:id="@+id/tv_group_item_city"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignTop="@id/iv_group_item"android:layout_marginTop="10dp"android:layout_toRightOf="@id/iv_group_item"android:text="郑州-新乡"android:textColor="#333"android:textSize="15sp"android:textStyle="bold"/><TextViewandroid:id="@+id/tv_group_item_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/tv_group_item_city"android:layout_marginTop="10dp"android:layout_toRightOf="@id/iv_group_item"android:text="09-25 15:21 出发"android:textColor="#757575"android:textSize="12sp"/><CheckBoxandroid:id="@+id/cb_group_item_flag"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_marginBottom="8dp"android:layout_marginLeft="12dp"android:background="@drawable/selector"android:button="@null"/><TextViewandroid:id="@+id/tv_group_item_price"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_marginRight="8dp"android:layout_marginTop="15dp"android:text="¥60元"android:textColor="#04a7dd"android:textSize="12dp"/><TextViewandroid:id="@+id/tv_group_item_state"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_below="@id/tv_group_item_price"android:layout_marginRight="8dp"android:layout_marginTop="12dp"android:text="进行中"android:textColor="#757575"android:textSize="12sp"/><TextViewandroid:id="@+id/tv_group_item_delete"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_alignParentRight="true"android:layout_marginBottom="8dp"android:layout_marginRight="8dp"android:text="退款"android:textColor="#fff"android:textSize="12sp"/><TextViewandroid:id="@+id/tv_group_item_judge"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_marginBottom="8dp"android:layout_marginRight="8dp"android:layout_toLeftOf="@id/tv_group_item_delete"android:text="二维码"android:textColor="#04a7dd"android:textSize="12sp"/></RelativeLayout>
</RelativeLayout>

如果仔细看上边的布局文件中的代码会发现布局文件中嵌套了两层RelativeLayout,这是写这个demo时踩到的一个大坑,如果不再添加一层RelativeLayout那么不管Item的高度设置为多少,item都只有固定的高度,如下图:

只有再添加一层RelativeLayout之后才能正常显示,如下:

ChildItem布局代码如下(ChildItem中同样需要多嵌套一层RelativeLayout,不然Item高度显示不正常):

<?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="match_parent"android:background="@drawable/shape4"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="100dp"><TextViewandroid:id="@+id/tv_child_item_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="8dp"android:layout_marginRight="15dp"android:layout_marginTop="12dp"android:text="姓名"android:textColor="#333"android:textSize="15sp"/><TextViewandroid:id="@+id/tv_child_item_person_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="12dp"android:layout_toRightOf="@id/tv_child_item_name"android:text="王先生"android:textColor="#757575"android:textSize="13sp"/><TextViewandroid:id="@+id/tv_child_item_touch_way"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="8dp"android:layout_alignParentBottom="true"android:layout_marginBottom="10dp"android:layout_marginTop="10dp"android:text="联系方式"android:textSize="15sp"/><TextViewandroid:id="@+id/tv_child_item_tel"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="15dp"android:layout_marginTop="10dp"android:layout_marginBottom="10dp"android:layout_toRightOf="@id/tv_child_item_touch_way"android:layout_alignParentBottom="true"android:text="16452135156"/><TextViewandroid:id="@+id/tv_child_item_state"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_marginRight="8dp"android:layout_marginTop="12dp"android:text="未上车"android:textColor="#04a7dd"android:textSize="12sp"/><TextViewandroid:id="@+id/tv_child_item_drawback"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_alignParentRight="true"android:layout_marginBottom="10dp"android:text="退款"android:layout_marginRight="8dp"android:textColor="#fff"/></RelativeLayout>
</RelativeLayout>

接下来最重要的一部分自定义ExpandableListView的适配器。

package com.example.edianzu.expandablelistviewdemo;import android.content.Context;
import android.database.DataSetObserver;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ExpandableListAdapter;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** Created by zhpan on 2016/9/24.*/public class MyAdapter implements ExpandableListAdapter,View.OnClickListener {private Context mContext;private List<GroupBean> mGroupBeanList;private List<List<ChildBean>> mLists;private Map<Integer,Boolean> map=new HashMap<>();//存储被选中的checkboxpublic MyAdapter(Context context, List<GroupBean> groupBeanList, List<List<ChildBean>> lists) {mContext = context;mGroupBeanList = groupBeanList;mLists = lists;}@Overridepublic void registerDataSetObserver(DataSetObserver observer) {}@Overridepublic void unregisterDataSetObserver(DataSetObserver observer) {}/*** 获取listview的组数* @return*/@Overridepublic int getGroupCount() {return 20;}/*** @param groupPosition 点击的条目位置* @return 子view条目总数数*/@Overridepublic int getChildrenCount(int groupPosition) {return 5;}/**** @param groupPosition* @return  此处应该返回GroupBean对象*/@Overridepublic Object getGroup(int groupPosition) {return mGroupBeanList.get(groupPosition);}/**** @param groupPosition* @param childPosition* @return  此处应该返回ChildBean对象*/@Overridepublic Object getChild(int groupPosition, int childPosition) {return mLists.get(childPosition);}@Overridepublic long getGroupId(int groupPosition) {return groupPosition;}@Overridepublic long getChildId(int groupPosition, int childPosition) {return childPosition;}@Overridepublic boolean hasStableIds() {return true;}/*** 返回的view作为组列表项* @param groupPosition* @param isExpanded* @param convertView* @param parent* @return*/@Overridepublic View getGroupView(final int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {final GroupViewHolder holder;if(convertView==null){holder=new GroupViewHolder();convertView=View.inflate(mContext,R.layout.group_item,null);holder.mImageViewLogo= (ImageView) convertView.findViewById(R.id.iv_group_item);holder.mCheckBoxFlag= (CheckBox) convertView.findViewById(R.id.cb_group_item_flag);holder.mTextViewCity= (TextView) convertView.findViewById(R.id.tv_group_item_city);holder.mTextViewTime= (TextView) convertView.findViewById(R.id.tv_group_item_time);holder.mTextViewPrice= (TextView) convertView.findViewById(R.id.tv_group_item_price);holder.mTextViewState= (TextView) convertView.findViewById(R.id.tv_group_item_state);holder.mTextViewDrawback= (TextView) convertView.findViewById(R.id.tv_group_item_delete);holder.mTextViewJudge= (TextView) convertView.findViewById(R.id.tv_group_item_judge);holder.mRelativeLayout= (RelativeLayout) convertView.findViewById(R.id.rl_group_item);holder.mCheckBoxFlag.setBackgroundResource(R.drawable.selector);holder.mImageViewLogo.setImageResource(R.drawable.mine_card);holder.mTextViewCity.setText("郑州-上海");holder.mTextViewPrice.setText("¥60元");holder.mTextViewTime.setText("09-25 15:21 出发");holder.mTextViewState.setText("二维码");holder.mTextViewDrawback.setText("退款");holder.mTextViewJudge.setBackgroundResource(R.drawable.shape3);holder.mTextViewDrawback.setBackgroundResource(R.drawable.shape2);convertView.setTag(holder);}else {holder= (GroupViewHolder) convertView.getTag();}holder.mCheckBoxFlag.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {@Overridepublic void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {if(holder.mCheckBoxFlag.isChecked()){map.put(groupPosition,true);}else {map.remove(groupPosition);}}});if(map!=null&&map.containsKey(groupPosition)){holder.mCheckBoxFlag.setChecked(true);}else {holder.mCheckBoxFlag.setChecked(false);}//  给mRelativeLayout设置点击事件拦截item点击展开子view的事件holder.mRelativeLayout.setOnClickListener(this);holder.mTextViewJudge.setOnClickListener(this);holder.mTextViewDrawback.setOnClickListener(this);/*** 三角的点击事件,点击三角时回调MainActivity中的OnFlagClickListener()回调方法*/holder.mCheckBoxFlag.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {MainActivity context = (MainActivity) mContext;context.OnFlagClickListener(groupPosition);}});return convertView;}/*** 返回的view对象作为子列表项* @param groupPosition* @param childPosition* @param isLastChild* @param convertView* @param parent* @return*/@Overridepublic View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {ChildViewHolder holder;if(convertView==null){convertView=View.inflate(mContext,R.layout.child_item,null);holder=new ChildViewHolder();holder.mTextViewName= (TextView) convertView.findViewById(R.id.tv_child_item_person_name);holder.mTextViewTel= (TextView) convertView.findViewById(R.id.tv_child_item_tel);holder.mTextViewState= (TextView) convertView.findViewById(R.id.tv_child_item_state);holder.mTextViewDrawback= (TextView) convertView.findViewById(R.id.tv_child_item_drawback);holder.mTextViewName.setText("王先生");holder.mTextViewTel.setText("16245895873");holder.mTextViewState.setText("未上车");holder.mTextViewDrawback.setText("退款");convertView.setTag(holder);}else {holder= (ChildViewHolder) convertView.getTag();}// 此处可以设置子view中控件的点击事件return convertView;}@Overridepublic boolean isChildSelectable(int groupPosition, int childPosition) {return true;}@Overridepublic boolean areAllItemsEnabled() {return false;}@Overridepublic boolean isEmpty() {return false;}@Overridepublic void onGroupExpanded(int groupPosition) {}@Overridepublic void onGroupCollapsed(int groupPosition) {}@Overridepublic long getCombinedChildId(long groupId, long childId) {return 0;}@Overridepublic long getCombinedGroupId(long groupId) {return 0;}/*** 控件的点击事件* @param v*/@Overridepublic void onClick(View v) {switch (v.getId()){case R.id.tv_group_item_judge:Toast.makeText(mContext, "点击了二维码", Toast.LENGTH_SHORT).show();break;case R.id.tv_group_item_delete:Toast.makeText(mContext, "点击了退款", Toast.LENGTH_SHORT).show();break;}}static class GroupViewHolder{private ImageView mImageViewLogo;private CheckBox mCheckBoxFlag;private TextView mTextViewCity;private TextView mTextViewTime;private TextView mTextViewPrice;private TextView mTextViewState;private TextView mTextViewJudge;private TextView mTextViewDrawback;private RelativeLayout mRelativeLayout;}static class ChildViewHolder{private TextView mTextViewName;private TextView mTextViewState;private TextView mTextViewTel;private TextView mTextViewDrawback;}
}

ExpandableListView的Adapter相比ListView的Adapter多了不少的方法,数了下大概是二十个!但是用得到的也就十个左右,用到的方法中都加了注释,这里不做过多解释。这里只说其中最重要的两个方法getGroupView()和getChildView()
这两个方法前者返回的view作为组列表项,后者返回的view对象作为子列表项。类比于ListView的Adapter中的getView()方法。

Adapter中还有一段代码需要作下解释:

 /*** 三角的点击事件,点击三角时回调MainActivity中的OnFlagClickListener()回调方法*/holder.mCheckBoxFlag.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {MainActivity context = (MainActivity) mContext;context.OnFlagClickListener(groupPosition);}});

这段代码是指示箭头的监听事件,指示箭头是自定义的一个CheckBox,然后用一个selector作为背景的。
当点击指示箭头的时候出发了回调方法,回调方法在MainActivity中完成展开和关闭子view的功能。
接口如下:

/*** Created by zhpan on 2016/9/24.* 三角的回调接口*/public interface SetOnFlagClickListener {/*** 三角点击事件的回调方法,点击三角时展开或关闭子ListView* @param position*/void OnFlagClickListener(int position);
}

MainActity实现该接口并重写OnFlagClickListener方法如下:

  /*** 三角点击事件的回调方法,点击三角时展开或关闭子ListView** @param position*/@Overridepublic void OnFlagClickListener(int position) {if (mExpandableListView.isGroupExpanded(position)) {  //如果是打开状态则关闭mExpandableListView.collapseGroup(position);} else { //如果是关闭状态则打开mExpandableListView.expandGroup(position);}}

可通过ExpandableListView的collapseGroup方法和expandGroup方法关闭和展开子view。
另外一点需要注意,默认的点击item就可以展开子View,但是这个例子中要屏蔽掉点击Item展开子View,想了很多办法都没实现,后来想既然给Item其他子控件设置了点击事件后子View可以获取相应点击事件而没有展开子View。于是就给Item的布局文件加了一个监听事件,然后在onClick()方法中什么都没有做,果真拦截了展开子View的时间。如下:

  //  给mRelativeLayout设置点击事件拦截item点击展开子view的事件holder.mRelativeLayout.setOnClickListener(this);

最后一点还要注意ExpandableListView中的指示箭头是一个CheckBox,当CheckBox状态改变的时候由于Item复用了convertView,会使CheckBox的选中状态错乱。因此Adapter中添加了存储CheckBox选中状态的代码。
关于CheckBox选中状态错乱的详细解释请参看上篇文章《ListView嵌套CheckBox滑动时CheckBox选中状态错乱》。

源码下载

好库推荐

给大家推荐一下BannerViewPager。这是一个基于ViewPager实现的具有强大功能的无限轮播库。通过BannerViewPager可以实现腾讯视频、QQ音乐、酷狗音乐、支付宝、天猫、淘宝、优酷视频、喜马拉雅、网易云音乐、哔哩哔哩等APP的Banner样式以及指示器样式。

欢迎大家到github关注BannerViewPager!

ExpandableListView实现可展开的ListView相关推荐

  1. Android开发之ExpandableListView: 可展开的ListView

    ExpandableListView: 可展开的ListView ListView 对于Android开发者来说是再熟悉不过的了,不过ListView的功能也有限.当需要展示第二层级数据时,使用Exp ...

  2. Android之ExpandableListView的各种效果(默认展开不合闭,自定义父栏目及箭头控制)

    1. 设置ExpandableListView 默认是展开的:  先实例化exListView (ExpandableListView所有数据齐全后可用,否则报错) exListView.setAda ...

  3. android listview 不显示_Android使用ExpandableListview实现时间轴

    背景 公司新项目中有一个功能要实现时间轴效果,网上也找了很多教程和Demo,但都不是我想要的,大部分用的是RecyclerView实现的,但这些都是一层的数据结构,但我需要的是两层的结构,用Recyc ...

  4. android中的高级组件(三)(ExpandableListView,ImageSwitcher,Gallery)

    ExpandableListView 分组列表视图 和ListView不同的是它是一个两级的滚动列表视图,每一个组可以展开,显示一些子项,类似于QQ列表,这些项目来至于ExpandableListAd ...

  5. 高级控件之分组列表视图(ExpandableListView)

    一.ExpandableListView的基础知识 和ListView不同的是它是一个两级的滚动列表视图,每一个组可以展开,显示一些子项,类似于 QQ列表,这些项目来至于ExpandableListA ...

  6. Android之实现QQ好友分组(ExpandableListView)

    在项目开发中,也许我们遇到过ListView中嵌套ListView,但谷歌建议我们最好别这样做,因此他们写好了一个ExpandableListView类,他继承ListView,可以实现ListVie ...

  7. 浅析——ExpandableListView的使用

    ExpandableListView(可扩展的ListView) ExpandableListVivew是ListView的子类,它在普通ListView的基础上进行了扩展,它把应用中的列表项分为几组 ...

  8. ExpandableListView详解

    文章目录 效果图 ExpandableListView的简介与使用 去掉ExpandableListView的箭头以及自定义Indicator 解决setOnChildClickListener失效问 ...

  9. Android ExpandableListView几个特殊的属性

    原帖地址:http://blog.csdn.net/t12x3456/article/details/7828620 1. 设置ExpandableListView 默认是展开的: 先实例化 exLi ...

最新文章

  1. 剑指offer:面试题12. 矩阵中的路径
  2. ArcMap 导入Excel坐标数据
  3. Android 自定义ViewPager设置屏蔽左右滑动事件
  4. 【连载】如何掌握openGauss数据库核心技术?秘诀二:拿捏执行器技术(1)
  5. python 公开课_【python公开课|学好python前,必须掌握这篇Python for 循环语句,还不会就快来看看】- 环球网校...
  6. 转:浅析 Java Thread.join()
  7. 433M无线通信模块通信应用场景和4G模块技术特点推荐篇
  8. Test meeting 11.23
  9. AI三大主义:符号主义、联结主义、行为主义
  10. 读懂K线,就能理解期货股票交易中人性的期望、猜疑、幻想、贪婪、恐惧...
  11. Flowable工作流之查询历史流程信息
  12. IBM WebSphere 9.0.5 笔记大全
  13. 瑞利信号公式(级联信道,多信道之和,多个瑞利信号之和,概率密度函数)
  14. 工具nmap常用命令总结
  15. 函数-function(函数的一般形式、命名、定义调用声明、函数的传递方式)
  16. 外泌体介绍 - MedChemExpress
  17. 2020-11-25博客营销及软文营销价值
  18. Java中为什么不能用“==”判断字符串是否相等
  19. u盘无法linux,Linux 3.18U盘无法正确使用
  20. PyBullet(六)UR5机器人手臂模型

热门文章

  1. Font Awesome 字体的以及 图标的使用总结
  2. Android集成Unity
  3. Centos 7无法SSH远程连接及解决方法
  4. 深度学习基础--分类网络
  5. Nginx+Flv:外网播放内网视频流 解决方案
  6. 计算机组装声卡,计算机组装与维护教程之声卡.pdf
  7. CrackMe 之 006
  8. ‍swf文件格式解析入门(tag解析)
  9. Windows XP系统优化超简单实用版
  10. Vue项目实战之电商后台管理系统(二) 主页模块