可扩展的下拉列表—ExpandableListView

ExpandableListView是可扩展的下拉列表,它的可扩展性在于点击父item可以拉下或收起列表,适用于一些场景的使用,下面介绍的是在Activity中如何使用。

下面介绍它的基本使用方法

先看一下效果:


一、最基本的使用

新建一个布局文件expandable_layout.xml,内容很简单,一个LinearLayout里面包含了一个ExpandableListView,别忘了给它加上id:

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical">  
  6.     <ExpandableListView  
  7.         android:id="@+id/expandablelistview"  
  8.         android:layout_margin="5dp"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="wrap_content" />  
  11. </LinearLayout>  

然后在activity文件中引入这个layout,获取这个ExpandableListView:

[java]  view plain  copy
  1. private ExpandableListView listview;  
[java]  view plain  copy
  1. setContentView(R.layout.expandable_layout);  
  2. listview = (ExpandableListView) findViewById(R.id.expandablelistview);  
为了给ExpandableListView提供数据,需要先初始化数据,这里使用一个Map来存放数据,类型为<String, List<String>>:
[java]  view plain  copy
  1. private Map<String, List<String>> dataset = new HashMap<>();  
  2. private String[] parentList = new String[]{"first""second""third"};  
  3. private List<String> childrenList1 = new ArrayList<>();  
  4. private List<String> childrenList2 = new ArrayList<>();  
  5. private List<String> childrenList3 = new ArrayList<>();  
[java]  view plain  copy
  1. private void initialData() {  
  2.     childrenList1.add(parentList[0] + "-" + "first");  
  3.     childrenList1.add(parentList[0] + "-" + "second");  
  4.     childrenList1.add(parentList[0] + "-" + "third");  
  5.     childrenList2.add(parentList[1] + "-" + "first");  
  6.     childrenList2.add(parentList[1] + "-" + "second");  
  7.     childrenList2.add(parentList[1] + "-" + "third");  
  8.     childrenList3.add(parentList[2] + "-" + "first");  
  9.     childrenList3.add(parentList[2] + "-" + "second");  
  10.     childrenList3.add(parentList[2] + "-" + "third");  
  11.     dataset.put(parentList[0], childrenList1);  
  12.     dataset.put(parentList[1], childrenList2);  
  13.     dataset.put(parentList[2], childrenList3);  
  14. }  
然后需要自己实现一个Adapte类,用于为ExpandableListView提供数据,该类继承了BaseExpandableListAdapter,下面这个是最简单的自定义的类:
[java]  view plain  copy
  1. private class MyExpandableListViewAdapter extends BaseExpandableListAdapter {  
  2.   
  3.     //  获得某个父项的某个子项  
  4.     @Override  
  5.     public Object getChild(int parentPos, int childPos) {  
  6.         return dataset.get(parentList[parentPos]).get(childPos);  
  7.     }  
  8.   
  9.     //  获得父项的数量  
  10.     @Override  
  11.     public int getGroupCount() {  
  12.         return dataset.size();  
  13.     }  
  14.   
  15.     //  获得某个父项的子项数目  
  16.     @Override  
  17.     public int getChildrenCount(int parentPos) {  
  18.         return dataset.get(parentList[parentPos]).size();  
  19.     }  
  20.   
  21.     //  获得某个父项  
  22.     @Override  
  23.     public Object getGroup(int parentPos) {  
  24.         return dataset.get(parentList[parentPos]);  
  25.     }  
  26.   
  27.     //  获得某个父项的id  
  28.     @Override  
  29.     public long getGroupId(int parentPos) {  
  30.         return parentPos;  
  31.     }  
  32.   
  33.     //  获得某个父项的某个子项的id  
  34.     @Override  
  35.     public long getChildId(int parentPos, int childPos) {  
  36.         return childPos;  
  37.     }  
  38.   
  39.     //  按函数的名字来理解应该是是否具有稳定的id,这个方法目前一直都是返回false,没有去改动过  
  40.     @Override  
  41.     public boolean hasStableIds() {  
  42.         return false;  
  43.     }  
  44.   
  45.     //  获得父项显示的view  
  46.     @Override  
  47.     public View getGroupView(int parentPos, boolean b, View view, ViewGroup viewGroup) {  
  48.         return view;  
  49.     }  
  50.   
  51.     //  获得子项显示的view  
  52.     @Override  
  53.     public View getChildView(int parentPos, int childPos, boolean b, View view, ViewGroup viewGroup) {  
  54.         return view;  
  55.     }  
  56.   
  57.     //  子项是否可选中,如果需要设置子项的点击事件,需要返回true  
  58.     @Override  
  59.     public boolean isChildSelectable(int i, int i1) {  
  60.         return false;  
  61.     }  
  62.  }  

其中注释说明了每个方法的作用,这个adapter的所有数据来源都是刚刚初始化的dataset,因此当这个adapter需要返回父项的数目时,返回的就是dataset的大小,如果需要返回某个父项的某个子项时,通过父项的position,使用map的get方法即可获得。自定义的类中最重要的是下面这两个方法,下面先介绍getGroupView方法:

[java]  view plain  copy
  1. //  获得父项显示的view  
  2. @Override  
  3. public View getGroupView(int parentPos, boolean b, View view, ViewGroup viewGroup) {  
  4.     if (view == null) {  
  5.         LayoutInflater inflater = (LayoutInflater) ExpandableListViewTestActivity  
  6.                 .this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
  7.         view = inflater.inflate(R.layout.parent_item, null);  
  8.     }  
  9.     view.setTag(R.layout.parent_item, parentPos);  
  10.     view.setTag(R.layout.child_item, -1);  
  11.     TextView text = (TextView) view.findViewById(R.id.parent_title);  
  12.     text.setText(parentList[parentPos]);  
  13.     return view;  
  14. }  

这个方法用来指定父项显示的样式,内容和行为,其中使用了parent_item布局:
[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent">  
  5.     <TextView  
  6.         android:id="@+id/parent_title"  
  7.         android:layout_width="match_parent"  
  8.         android:layout_height="wrap_content"  
  9.         android:textSize="20sp"  
  10.         android:textColor="@color/black"  
  11.         android:textStyle="bold"  
  12.         android:text="这是父item"  
  13.         android:layout_margin="5dp"/>  
  14. </LinearLayout>  
布局很简单,只是一个LinearLayout包含了一个TextView。

getGroupView方法先判断view是否为空,如果view不为空,说明已经加载过一次parent_item布局,因此不需要重复加载以提高效率。如果view为空,那么使用如下方法加载parent_item布局:

[java]  view plain  copy
  1. if (view == null) {  
  2.     LayoutInflater inflater = (LayoutInflater) ExpandableListViewTestActivity  
  3.                    .this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
  4.     view = inflater.inflate(R.layout.parent_item, null);  
  5. }  

然后定义父项的内容和行为,这里只需要定义父项的内容,先通过id获得parent_item布局中的TextView,然后通过方法提供的parentPos参数获取到存放在dataset中的内容,调用TextView的setText方法即可设置父项要显示的内容:
 
 
[java] view plain copy
  1. TextView text = (TextView) view.findViewById(R.id.parent_title);  
  2. text.setText(parentList[parentPos]);  

然后将view返回即可。

接下来是getChildView方法:

[java]  view plain  copy
  1. //  获得子项显示的view  
  2. @Override  
  3. public View getChildView(int parentPos, int childPos, boolean b, View view, ViewGroup viewGroup) {  
  4.     if (view == null) {  
  5.         LayoutInflater inflater = (LayoutInflater) ExpandableListViewTestActivity  
  6.                 .this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
  7.         view = inflater.inflate(R.layout.child_item, null);  
  8.     }  
  9.     view.setTag(R.layout.parent_item, parentPos);  
  10.     view.setTag(R.layout.child_item, childPos);  
  11.     TextView text = (TextView) view.findViewById(R.id.child_title);  
  12.     text.setText(dataset.get(parentList[parentPos]).get(childPos));  
  13.     text.setOnClickListener(new View.OnClickListener() {  
  14.         @Override  
  15.         public void onClick(View view) {  
  16.             Toast.makeText(ExpandableListViewTestActivity.this"点到了内置的textview", Toast.LENGTH_SHORT).show();  
  17.         }  
  18.     });  
  19.     return view;  
  20. }  
这个方法用于定义子项的布局,内容和行为,同样需要一个child_item来指定子项的布局:

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent">  
  5.     <TextView  
  6.         android:id="@+id/child_title"  
  7.         android:layout_width="wrap_content"  
  8.         android:layout_height="wrap_content"  
  9.         android:textSize="18sp"  
  10.         android:textColor="@color/black"  
  11.         android:text="这是子item"  
  12.         android:layout_margin="5dp"/>  
  13. </LinearLayout>  
关于view的处理和父项一样,这里在方法中还给子项显示内容的textview添加了一个点击的监听器。

adapter自定义完之后,通过声明一个Adapter,实例化后调用setAdapter方法即可:

[java]  view plain  copy
  1. private MyExpandableListViewAdapter adapter;  
[java]  view plain  copy
  1. adapter = new MyExpandableListViewAdapter();  
  2. listview.setAdapter(adapter);  
至此,最基本的ExpandableListView的使用就可以满足啦

二、稍微复杂一点的使用方法

1、为子项添加点击的监听事件,效果图:


只需要调用ExpandableListView的setOnChildClickListener方法即可:

[java]  view plain  copy
  1. listview.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {  
  2.     @Override  
  3.     public boolean onChildClick(ExpandableListView expandableListView, View view,  
  4.                                 int parentPos, int childPos, long l) {  
  5.         Toast.makeText(ExpandableListViewTestActivity.this,  
  6.                 dataset.get(parentList[parentPos]).get(childPos), Toast.LENGTH_SHORT).show();  
  7.         return true;  
  8.     }  
  9. });  

这里在每个子项被点击了之后会显示是哪个子项被点击了
特别注意
(1)在使用这个方法的时候需要将自定义的adapter中的isChildSelectable方法的返回值设置为true,否则子项的点击不生效,但子项布局中设置的控件的监听器依然可以生效。
 
 
[java] view plain copy
  1. //  子项是否可选中,如果需要设置子项的点击事件,需要返回true  
  2. @Override  
  3. public boolean isChildSelectable(int i, int i1) {  
  4.     return true;  
  5. }  
(2)如果在子项中对某个控件设置了监听器,这个控件要注意不能铺满整个子项,所以在设置高度和宽度时要特别注意,否则设置了子项的监听器也是没有用的

2、为子项添加长按的监听器
在ExpandableListView中并没有提供设置子项长按监听器的方法,多方查找之后找到了一个算是比较靠谱的方法,目前用起来暂时还没有什么问题,有问题再来更新这篇文章。
效果图:

ExpandableListView中关于长按有一个setOnItemLongClickListener方法,但是这个方法有个问题,没办法区分被长按的item是父项还是子项,所以需要在自定义adapter的getGroupView方法和getChildView方法中加一点东西来区分是父项还是子项:
完整的getGroupView方法和getChildView方法:

  
  
[java] view plain copy
  1. //  获得父项显示的view  
  2. @Override  
  3. public View getGroupView(int parentPos, boolean b, View view, ViewGroup viewGroup) {  
  4.     if (view == null) {  
  5.         LayoutInflater inflater = (LayoutInflater) ExpandableListViewTestActivity  
  6.                 .this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
  7.         view = inflater.inflate(R.layout.parent_item, null);  
  8.     }  
  9.     view.setTag(R.layout.parent_item, parentPos);  
  10.     view.setTag(R.layout.child_item, -1);  
  11.     TextView text = (TextView) view.findViewById(R.id.parent_title);  
  12.     text.setText(parentList[parentPos]);  
  13.     return view;  
  14. }  
  15.   
  16. //  获得子项显示的view  
  17. @Override  
  18. public View getChildView(int parentPos, int childPos, boolean b, View view, ViewGroup viewGroup) {  
  19.     if (view == null) {  
  20.         LayoutInflater inflater = (LayoutInflater) ExpandableListViewTestActivity  
  21.                 .this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
  22.         view = inflater.inflate(R.layout.child_item, null);  
  23.     }  
  24.     view.setTag(R.layout.parent_item, parentPos);  
  25.     view.setTag(R.layout.child_item, childPos);  
  26.     TextView text = (TextView) view.findViewById(R.id.child_title);  
  27.     text.setText(dataset.get(parentList[parentPos]).get(childPos));  
  28.     text.setOnClickListener(new View.OnClickListener() {  
  29.         @Override  
  30.         public void onClick(View view) {  
  31.             Toast.makeText(ExpandableListViewTestActivity.this"点到了内置的textview",  
  32.                     Toast.LENGTH_SHORT).show();  
  33.         }  
  34.     });  
  35.     return view;  
  36. }  
这里用到了view的setTag方法,一共设置了两个Tag,标签虽然在设置的时候提示说只要int类型即可,但一开始使用0和1来做tag的时候,显示没有报错,但编译运行就报错了,要求是资源文件的id才行,因此换成了R.layout.parent_item和R.layout.child_item。
如果是父项,就设置R.layout.parent_item为第几个父项,设置R.layout.child_item为-1。如果是子项,就设置R.layout.parent_item属于第几个父项,设置R.layout.child_item为该父项的第几个子项,这样就可以区分被长按的是父项还是子项了。
然后设置ExpandableListView长按item的监听器:

  
  
[java] view plain copy
  1. listview.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {  
  2.             @Override  
  3.             public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {  
  4.                 String content = "";  
  5.                 if ((int) view.getTag(R.layout.child_item) == -1) {  
  6.                     content = "父类第" + view.getTag(R.layout.parent_item) + "项" + "被长按了";  
  7.                 } else {  
  8.                     content = "父类第" + view.getTag(R.layout.parent_item) + "项" + "中的"  
  9.                             + "子类第" + view.getTag(R.layout.child_item) + "项" + "被长按了";  
  10.                 }  
  11.                 Toast.makeText(ExpandableListViewTestActivity.this, content, Toast.LENGTH_SHORT).show();  
  12.                 return true;  
  13.             }  
  14.  });  
这样就可以区分父项和子项了。
3、更新数据
列表项更新数据一般使用的都是adapter的notifyDataSetChanged()方法,ExpandableListView也不例外,下面展示一下怎么更新数据。
效果图:

首先要在原先的expandable_layout.xml文件中添加一个按钮,用来更新数据:

  
  
[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical">  
  6.     <ExpandableListView  
  7.         android:id="@+id/expandablelistview"  
  8.         android:layout_margin="5dp"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="wrap_content" />  
  11.     <Button  
  12.         android:id="@+id/updateData"  
  13.         android:layout_width="wrap_content"  
  14.         android:layout_height="wrap_content"  
  15.         android:layout_margin="10dp"  
  16.         android:layout_gravity="center"  
  17.         android:text="刷新数据"/>  
  18. </LinearLayout>  
然后在Activity文件中引入这个Button:

  
  
[java] view plain copy
  1. private Button button;  
[java] view plain copy
  1. button = (Button) findViewById(R.id.updateData);  
为button设置点击的监听器:

  
  
[java] view plain copy
  1. button.setOnClickListener(new View.OnClickListener() {  
  2.     @Override  
  3.     public void onClick(View view) {  
  4.         updateData();  
  5.         Toast.makeText(ExpandableListViewTestActivity.this"数据已更新", Toast.LENGTH_SHORT).show();  
  6.     }  
  7. });  
其中updateData()方法就是用于更新ExpandableListView的方法,看一下具体的实现:

  
  
[java] view plain copy
  1. /** 
  2.  * 更新数据 
  3.  */  
  4. private void updateData() {  
  5.     childrenList1.clear();  
  6.     childrenList1.add(parentList[0] + "-new-" + "first");  
  7.     childrenList1.add(parentList[0] + "-new-" + "second");  
  8.     childrenList1.add(parentList[0] + "-new-" + "third");  
  9.     childrenList2.clear();  
  10.     childrenList2.add(parentList[1] + "-new-" + "first");  
  11.     childrenList2.add(parentList[1] + "-new-" + "second");  
  12.     childrenList2.add(parentList[1] + "-new-" + "third");  
  13.     childrenList3.clear();  
  14.     childrenList3.add(parentList[2] + "-new-" + "first");  
  15.     childrenList3.add(parentList[2] + "-new-" + "second");  
  16.     childrenList3.add(parentList[2] + "-new-" + "third");  
  17.     adapter.notifyDataSetChanged();  
  18. }  
还记得上面初始化数据的时候使用的childrenList1、childrenList2、childrenList3吗,这三个列表是我们用来放子项列表,然后已经添加到dataset里面去了。这个时候无需再用dataset添加一次,只需要将列表清空,添加上更新的内容即可。也可以根据自己的需求选择性地删除一些东西来获得新的子项列表。
需要注意的是,子项列表在内存中的地址是不可以改变的,不能使用形如childrenList1 = new ArrayList<>();这样的方法来获的新列表,这样会使childrenList1在内存中的地址发生改变,导致调用adapter的notifyDataSetChanged()方法时不生效。所以如果想要清空这个列表项时,使用childrenList1.clear()方法,这样才能保证顺利更新列表。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值