• 29. 【Android教程】折叠列表 ExpandableListView


    本节学习一个可折叠的 ListView,可以用在一些需要分类的场景下。通过 ExpandableListView 我们可以首先在 ListView 上展示大的分类,当点击某个类别的时候再将 ListView 做一个展开,展示该类下的所有子类供用户选择。它与 ListView 的不同主要是 ExpandableListView 提供了两级列表,可以方便的做伸展和收缩。

    1. ExpandableListView 的特性

    ExpandableListView 继承自 ListView,这意味着它拥有 ListView 的所有属性,是 ListView 的升级版。它在 ListView 的基础上增加了子列表,当我们点击某个列表项的时候,它会展开显示所有的子 item;当我们再次点击该列表项的时候,它会收缩隐藏所有的子 item,其中子 item 相当于是一个 ListView,我们可以给它设置不同的列表样式及点击事件,通常适用于有两级分类并且子类比较多的列表场景。

    2. ExpandableListView 的基本使用方法

    2.1 常用属性

    • android:childDivider:
      设置子列表项的分割线样式,可以通过 drawable 或者 color 资源的方式进行配置
    • android:childIndicator:
      设置显示在子列表项旁边的 View,一般用作该列表项的指示标注
    • android:childIndicatorEnd:
      设置子列表指示View的终止位置边界
    • android:childIndicatorLeft:
      设置子列表指示View的左边界
    • android:childIndicatorRight:
      设置子列表指示View的右边界
    • android:childIndicatorStart:
      设置子列表指示View的起始位置边界
    • android:groupIndicator:
      当前分类组旁边的指示 View
    • android:indicatorEnd:
      指示 View 的终止位置边界
    • android:indicatorLeft:
      指示 View 的左边界
    • android:indicatorRight:
      指示 View 的右边界
    • android:indicatorStart:
      指示 View 的起始位置边界

    2.2 常用 API

    • setChildIndicator(Drawable):
      设置展示在子列表项旁边的指示 View 的样式资源
    • setGroupIndicator(Drawable) :
      设置展示在主列表项旁边的指示 View 的样式资源,这个不会因为主列表项的伸展或者收缩而改变
    • getGroupView():
      返回这一组列表的头 View
    • getChildView():
      返回列表的子列表项

    2.3 事件监听器

    • ExpandableListView.OnChildClickListener:
      该接口当中只有一个回调方法:

      public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id)
      

      当我们点击一个子列表项的时候会回调此方法,参数解析

      • ExpandableListView parent:被点击的 ExpandableListView 对象
      • View v:被点击的具体 item 对象
      • int groupPosition:被点击的 item 所在组在主列表的位置
      • int childPosition:被点击的 item 在当前组内的位置
    • ExpandableListView.OnGroupClickListener:
      接口中只有一个回调方法:

      public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id)
      

    该方法监听某个组的点击事件,当该组内有任意 item 被点击是回调,参数详情参见onGroupClick方法的解析

    • ExpandableListView.OnGroupCollapseListener:
      只需要实现一个方法:
    public void onGroupCollapse(int groupPosition)
    

    当某个组被折叠收缩的时候会回调此方法,参数表示被收缩的组在整个主列表中的位置

    • ExpandableListView.OnGroupExpandListener:
      该接口同样是需要实现一个方法:
      public void onGroupExpand(int groupPosition)
      

    当某个组被展开的时候回调此方法

    3. ExpandableListView 示例

    ExpandableListView 主要是在 ListView 的基础之上加上了折叠的分类效果,所以本节就通过 ExpandableListView 实现对数据的二级分类列表效果,大类就用大家比较熟悉的某竞技游戏里面的英雄分类,而子类就是该类别里面的几个英雄。
    PS:英雄分类仁者见仁智者见智,青铜选手求各位骨灰玩家轻拍

    3.1 编写 Activity 的布局文件

    和前几节的例子一样,我们仅需要在根布局中防止一个 ExpandableListView 即可,然后设置上相应的属性,如下:

    1. <ExpandableListView xmlns:android="http://schemas.android.com/apk/res/android"
    2. android:id="@+id/expandableListView"
    3. android:layout_width="match_parent"
    4. android:layout_height="match_parent"
    5. android:divider="@android:color/darker_gray"
    6. android:dividerHeight="0.5dp"
    7. android:indicatorLeft="?android:attr/expandableListPreferredItemIndicatorLeft"
    8. android:padding="30dp" />

    3.2 编写列表布局

    列表布局类似 ListView 里面的 item 布局,但是由于 ExpandableListView 有主类和子类区分,所以这里需要提供两套布局以适应主列表和展开后的子列表:

    • 主列表布局 list_group.xml :
      1. <TextView xmlns:android="http://schemas.android.com/apk/res/android"
      2. android:id="@+id/listTitle"
      3. android:layout_width="fill_parent"
      4. android:layout_height="wrap_content"
      5. android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
      6. android:paddingTop="10dp"
      7. android:paddingBottom="10dp"
      8. android:textColor="@android:color/black" />

    为了突出大分类,字体设置为黑体。

    • 子列表布局 list_item.xml :
      1. <?xml version="1.0" encoding="utf-8"?>
      2. <TextView xmlns:android="http://schemas.android.com/apk/res/android"
      3. android:id="@+id/expandedListItem"
      4. android:layout_width="fill_parent"
      5. android:layout_height="wrap_content"
      6. android:paddingLeft="?android:attr/expandableListPreferredChildPaddingLeft"
      7. android:paddingTop="10dp"
      8. android:paddingBottom="10dp" />

    3.3 编写数据集合

    本节数据会相对较多,并且有两级分类,为了代码结构清晰这里将数据单独抽离出来,与 Activity 的业务代码隔离开,新建一个数据集类 DataCollection.java:

    1. package com.emercy.myapplication;
    2. import java.util.ArrayList;
    3. import java.util.HashMap;
    4. import java.util.List;
    5. public class DataCollection {
    6. // 通过map存放每一个大类,key是大类类别名,value是子类List
    7. private static HashMap<String, List<String>> mExpandableListData = new HashMap<>();
    8. private static final String MASTER = "法师";
    9. private static final String ASSASSINATOR = "刺客";
    10. private static final String SHOOTER = "射手";
    11. private static final String TANK = "对抗";
    12. private static final String ASSIST = "辅助";
    13. // 类加载的时候初始化数据
    14. static {
    15. // 创建子类列表,存放在List当中
    16. List<String> master = new ArrayList<>();
    17. master.add("安琪拉");
    18. master.add("西施");
    19. master.add("沈梦溪");
    20. master.add("嫦娥");
    21. master.add("上官婉儿");
    22. master.add("不知火舞");
    23. List<String> assassinator = new ArrayList<>();
    24. assassinator.add("马超");
    25. assassinator.add("镜");
    26. assassinator.add("兰陵王");
    27. assassinator.add("孙悟空");
    28. assassinator.add("娜可露露");
    29. assassinator.add("元歌");
    30. List<String> shooter = new ArrayList<>();
    31. shooter.add("狄仁杰");
    32. shooter.add("伽罗");
    33. shooter.add("蒙犽");
    34. shooter.add("鲁班七号");
    35. shooter.add("孙尚香");
    36. shooter.add("后羿");
    37. List<String> tank = new ArrayList<>();
    38. // 咦?为什么马超出现了两次?
    39. // 因为作者就叫马超
    40. tank.add("马超");
    41. tank.add("盖伦");
    42. tank.add("芈月");
    43. tank.add("铠");
    44. tank.add("典韦");
    45. List<String> assist = new ArrayList<>();
    46. assist.add("蔡文姬");
    47. assist.add("小明");
    48. assist.add("庄周");
    49. assist.add("鲁班");
    50. assist.add("东皇太一");
    51. // 将所有的子类List作为Value存放到大类中
    52. mExpandableListData.put(MASTER, master);
    53. mExpandableListData.put(ASSASSINATOR, assassinator);
    54. mExpandableListData.put(SHOOTER, shooter);
    55. mExpandableListData.put(TANK, tank);
    56. mExpandableListData.put(ASSIST, assist);
    57. }
    58. static HashMap<String, List<String>> getData() {
    59. return mExpandableListData;
    60. }
    61. }

    该类是一个静态工具类,里面只有一个静态成员变量,用一个 map 来保存所有的列表项。map 的 key 是大类的类别名称,value 是子类的 List;子类通过一个 List 来存储所有的子类 item,最后通过getData()接口对外暴露数据集合。

    3.4 编写 Adapter

    ExpandableListView 的 Adapter 有些不一样,因为它需要区分主类别和子类别,会多一个 group 的概念,这里采用的是 BaseExpandableListAdapter。相比前几节使用的 baseAdapter 大体上的回调方法都类似,只是多了一些对 group 的处理。

    比如 baseAdapter 的getView在 BaseExpandableListAdapter 里面分成了getGroupViewgetChildView分别用来设置主类别的 item 和子类别的 item。结合 BaseAdapter 的回调方法不难理解 BaseExpandableListAdapter,代码如下:

    1. package com.emercy.myapplication;
    2. import android.content.Context;
    3. import android.graphics.Typeface;
    4. import android.view.LayoutInflater;
    5. import android.view.View;
    6. import android.view.ViewGroup;
    7. import android.widget.BaseExpandableListAdapter;
    8. import android.widget.TextView;
    9. import java.util.HashMap;
    10. import java.util.List;
    11. public class MyExpandableListAdapter extends BaseExpandableListAdapter {
    12. private Context mContext;
    13. private List mHeroCategory;
    14. private HashMap> mHeroName;
    15. public MyExpandableListAdapter(Context context, List expandableListTitle,
    16. HashMap> expandableListDetail) {
    17. mContext = context;
    18. mHeroCategory = expandableListTitle;
    19. mHeroName = expandableListDetail;
    20. }
    21. @Override
    22. public Object getChild(int groupPosition, int childPosition) {
    23. return mHeroName.get(mHeroCategory.get(groupPosition)).get(childPosition);
    24. }
    25. @Override
    26. public long getChildId(int groupPosition, int childPosition) {
    27. return childPosition;
    28. }
    29. @Override
    30. public View getChildView(int groupPosition, final int childPosition,
    31. boolean isLastChild, View convertView, ViewGroup parent) {
    32. final String expandedListText = (String) getChild(groupPosition, childPosition);
    33. if (convertView == null) {
    34. LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    35. convertView = layoutInflater.inflate(R.layout.list_item, null);
    36. }
    37. TextView expandedListTextView = convertView.findViewById(R.id.expandedListItem);
    38. expandedListTextView.setText(expandedListText);
    39. return convertView;
    40. }
    41. @Override
    42. public int getChildrenCount(int groupPosition) {
    43. return mHeroName.get(mHeroCategory.get(groupPosition)).size();
    44. }
    45. @Override
    46. public Object getGroup(int groupPosition) {
    47. return mHeroCategory.get(groupPosition);
    48. }
    49. @Override
    50. public int getGroupCount() {
    51. return mHeroCategory.size();
    52. }
    53. @Override
    54. public long getGroupId(int listPosition) {
    55. return listPosition;
    56. }
    57. @Override
    58. public View getGroupView(int groupPosition, boolean isExpanded,
    59. View convertView, ViewGroup parent) {
    60. String listTitle = (String) getGroup(groupPosition);
    61. if (convertView == null) {
    62. LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    63. convertView = layoutInflater.inflate(R.layout.list_group, null);
    64. }
    65. TextView listTitleTextView = convertView
    66. .findViewById(R.id.listTitle);
    67. listTitleTextView.setTypeface(null, Typeface.BOLD);
    68. listTitleTextView.setText(listTitle);
    69. return convertView;
    70. }
    71. @Override
    72. public boolean hasStableIds() {
    73. return false;
    74. }
    75. @Override
    76. public boolean isChildSelectable(int groupPosition, int childPosition) {
    77. return true;
    78. }
    79. }

    如果有对这些回调接口的实现不太理解的,可以回顾一下第24节中讲 ListView 的时候对 BaseAdapter 做的详细讲解。

    3.5 编写 MainActivity

    前面已经实现了布局、数据、适配器等模块的编写,整个 ExpandableListView 的框架就已经搭建完毕了。虽然本节的示例比较简单,代码量也比较少,但是也希望大家在学习过程中能够注重模块的编写顺序,循序渐进的培养自己搭建一个更完整的更大型架构的能力。
    框架搭建完毕就可以进入业务代码的编写了,在MainActivity中我们主要做以下4件事:

    1. 设置布局文件并从布局文件中拿到 ExpandableListView 实例;
    2. 获取数据集(实际使用中可能是从网络获取或者本地读取);
    3. 创建适配器,并为 ExpandableListView 实例设置适配器;
    4. 为 ExpandableListView 添加相应的事件监听器,并实现监听器接口中的回调方法。

    按照以上 4 步来做即可,代码如下:

    1. package com.emercy.myapplication;
    2. import android.app.Activity;
    3. import android.os.Bundle;
    4. import android.view.View;
    5. import android.widget.ExpandableListAdapter;
    6. import android.widget.ExpandableListView;
    7. import android.widget.Toast;
    8. import java.util.ArrayList;
    9. import java.util.HashMap;
    10. import java.util.List;
    11. public class MainActivity extends Activity {
    12. HashMap> expandableListDetail;
    13. @Override
    14. protected void onCreate(Bundle savedInstanceState) {
    15. super.onCreate(savedInstanceState);
    16. // 1.设置布局文件并从布局文件中拿到 ExpandableListView 实例;
    17. setContentView(R.layout.activity_main);
    18. ExpandableListView listView = findViewById(R.id.expandableListView);
    19. // 2. 获取数据集(实际使用中可能是从网络获取或者本地读取)
    20. expandableListDetail = DataCollection.getData();
    21. final List heroCategory = new ArrayList<>(expandableListDetail.keySet());
    22. // 3. 创建适配器,并为 ExpandableListView 实例设置适配器
    23. ExpandableListAdapter adapter = new MyExpandableListAdapter(this, heroCategory, expandableListDetail);
    24. listView.setAdapter(adapter);
    25. // 4. 为 ExpandableListView 添加相应的事件监听器,并实现监听器接口中的回调方法
    26. listView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
    27. @Override
    28. public void onGroupExpand(int groupPosition) {
    29. Toast.makeText(getApplicationContext(), heroCategory.get(groupPosition)
    30. + " 列表展开", Toast.LENGTH_SHORT).show();
    31. }
    32. });
    33. listView.setOnGroupCollapseListener(new ExpandableListView.OnGroupCollapseListener() {
    34. @Override
    35. public void onGroupCollapse(int groupPosition) {
    36. Toast.makeText(getApplicationContext(), heroCategory.get(groupPosition)
    37. + " 列表折叠", Toast.LENGTH_SHORT).show();
    38. }
    39. });
    40. listView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
    41. @Override
    42. public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
    43. Toast.makeText(getApplicationContext(), heroCategory.get(groupPosition)
    44. + " -> " + expandableListDetail.get(heroCategory.get(groupPosition))
    45. .get(childPosition), Toast.LENGTH_SHORT
    46. ).show();
    47. return false;
    48. }
    49. });
    50. }
    51. }

    编译运行之后,界面上会展示一个 5 大英雄类别的 ListView,点击每个类别系统会回调onGroupExpand方法,我们在当中打印出当前被展开的类别名;然后会弹出该类下的英雄名称,点击英雄名称系统会回调onChildClick方法,我们在方法中打印出被点击的英雄名称;最后我们可以点击已经展开的英雄类别,系统会将点击的类别恢复折叠状态同时回调onGroupCollapse方法,在其中我们打印出被折叠的类别名称,最终效果如下:

    4. 小结

    本节学习了 ListView 的升级版,ExpandableListView 继承自 ListView,在 ListView 的基础之上加上了二级分类,所以引入了 group 的概念。在布局文件中除了正常的列表 item 外还需要有一个 group 的布局;

    ExpandableListAdapter 也多了一些针对 group 的处理;数据也需要分主类别和子类别,我们先将英雄分为 5 大类,接着在 5 个大类下分别列举了一些该类的英雄名称,最终通过 ExpandableListAdapter 实现了一个英雄分类的示例 App。

  • 相关阅读:
    【算法】Median of Two Sorted Arrays
    Java实习生常规技术面试题每日十题Java基础(六)
    2023计算机毕业设计SSM最新选题之java乡村疫情防控管理系统37804
    FluentValidation在C# WPF中的应用
    final 在 java 中有什么作用?
    Oracle中序列
    C++面试题之C++中的指针参数传递和引用参数传递
    企业级高负载WEB服务器—Tomcat
    Python|Pyppeteer实现持久化使用cookie的方法(19)
    0-5V转4-20mA电路
  • 原文地址:https://blog.csdn.net/u014316335/article/details/137878627