Android ExpandableListView 详细用法全解析

引言

在 Android 开发中,列表展示是一种非常常见的交互形式。而 ExpandableListView 作为一种特殊的列表控件,它允许我们创建具有分组功能的列表,每个分组下还可以包含多个子项,并且分组可以展开和收缩,这大大增强了数据展示的灵活性和可读性。本文将详细介绍 ExpandableListView 的用法,从基本概念到具体实现,再到高级应用,帮助你全面掌握这一控件。

1. ExpandableListView 基本概念

ExpandableListView 继承自 ListView,是 Android 提供的一种可展开的列表视图。它主要由分组(Group)和子项(Child)两部分组成。每个分组可以包含多个子项,用户可以通过点击分组来展开或收缩其对应的子项列表。这种控件适用于需要对数据进行分类展示的场景,例如联系人列表按照字母分组、商品列表按照类别分组等。

2. 布局文件中添加 ExpandableListView

首先,我们需要在布局文件中添加 ExpandableListView 控件。以下是一个简单的示例:

bash 复制代码
<!-- activity_main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ExpandableListView
        android:id="@+id/expandableListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

在这个布局中,我们创建了一个垂直的线性布局,并在其中添加了一个 ExpandableListView,其宽度和高度都设置为 match_parent,以填充整个父布局。

3. 创建数据模型

为了展示分组和子项的数据,我们需要创建相应的数据模型类。假设我们要展示一个水果分类列表,每个分类下有不同的水果,我们可以创建如下的数据模型类:

bash 复制代码
// GroupModel.java
public class GroupModel {
    private String groupName;

    public GroupModel(String groupName) {
        this.groupName = groupName;
    }

    public String getGroupName() {
        return groupName;
    }

    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }
}

// ChildModel.java
public class ChildModel {
    private String childName;

    public ChildModel(String childName) {
        this.childName = childName;
    }

    public String getChildName() {
        return childName;
    }

    public void setChildName(String childName) {
        this.childName = childName;
    }
}

GroupModel 类用于表示分组信息,包含一个分组名称;ChildModel 类用于表示子项信息,包含一个子项名称。

4. 创建适配器

ExpandableListView 需要使用适配器来将数据绑定到视图上。我们可以继承 BaseExpandableListAdapter 类来创建自定义适配器。以下是一个示例:

bash 复制代码
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.TextView;

import java.util.List;

public class MyExpandableListAdapter extends BaseExpandableListAdapter {

    private Context context;
    private List<GroupModel> groupList;
    private List<List<ChildModel>> childList;

    public MyExpandableListAdapter(Context context, List<GroupModel> groupList, List<List<ChildModel>> childList) {
        this.context = context;
        this.groupList = groupList;
        this.childList = childList;
    }

    @Override
    public int getGroupCount() {
        return groupList.size();
    }

    @Override
    public int getChildrenCount(int groupPosition) {
        return childList.get(groupPosition).size();
    }

    @Override
    public Object getGroup(int groupPosition) {
        return groupList.get(groupPosition);
    }

    @Override
    public Object getChild(int groupPosition, int childPosition) {
        return childList.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) {
        GroupModel groupModel = (GroupModel) getGroup(groupPosition);
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(android.R.layout.simple_expandable_list_item_1, null);
        }
        TextView groupTextView = convertView.findViewById(android.R.id.text1);
        groupTextView.setText(groupModel.getGroupName());
        return convertView;
    }

    @Override
    public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
        ChildModel childModel = (ChildModel) getChild(groupPosition, childPosition);
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(android.R.layout.simple_list_item_1, null);
        }
        TextView childTextView = convertView.findViewById(android.R.id.text1);
        childTextView.setText(childModel.getChildName());
        return convertView;
    }

    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return true;
    }
}

在这个适配器中,我们需要实现一系列的方法:

  • getGroupCount():返回分组的数量。
  • getChildrenCount(int groupPosition):返回指定分组下子项的数量。
  • getGroup(int groupPosition):返回指定位置的分组对象。
  • getChild(int groupPosition, int childPosition):返回指定分组下指定位置的子项对象。
  • getGroupId(int groupPosition) 和 getChildId(int groupPosition, int childPosition):分别返回分组和子项的 ID。
  • getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent):创建并返回分组的视图。
  • getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent):创建并返回子项的视图。
  • isChildSelectable(int groupPosition, int childPosition):指定子项是否可以被选中。

5. 在 Activity 中使用 ExpandableListView

在 Activity 中,我们需要初始化数据、创建适配器并将其设置给 ExpandableListView。以下是示例代码:

bash 复制代码
import android.os.Bundle;
import android.widget.ExpandableListView;

import androidx.appcompat.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private ExpandableListView expandableListView;
    private MyExpandableListAdapter adapter;
    private List<GroupModel> groupList;
    private List<List<ChildModel>> childList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        expandableListView = findViewById(R.id.expandableListView);

        // 初始化数据
        initData();

        // 创建适配器
        adapter = new MyExpandableListAdapter(this, groupList, childList);

        // 设置适配器
        expandableListView.setAdapter(adapter);
    }

    private void initData() {
        groupList = new ArrayList<>();
        childList = new ArrayList<>();

        // 添加分组数据
        GroupModel group1 = new GroupModel("热带水果");
        GroupModel group2 = new GroupModel("温带水果");
        groupList.add(group1);
        groupList.add(group2);

        // 为每个分组添加子项数据
        List<ChildModel> childList1 = new ArrayList<>();
        childList1.add(new ChildModel("香蕉"));
        childList1.add(new ChildModel("芒果"));
        childList.add(childList1);

        List<ChildModel> childList2 = new ArrayList<>();
        childList2.add(new ChildModel("苹果"));
        childList2.add(new ChildModel("梨"));
        childList.add(childList2);
    }
}

在 onCreate 方法中,我们首先获取 ExpandableListView 控件的引用,然后调用 initData 方法初始化数据,接着创建适配器并将其设置给 ExpandableListView。

6. 处理分组和子项的点击事件

我们可以为 ExpandableListView 添加分组和子项的点击事件监听器,以实现相应的交互逻辑。以下是示例代码:

bash 复制代码
// 在 onCreate 方法中添加以下代码
expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
    @Override
    public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
        // 处理分组点击事件
        GroupModel groupModel = (GroupModel) adapter.getGroup(groupPosition);
        String groupName = groupModel.getGroupName();
        // 这里可以添加自定义的逻辑,比如弹出提示框显示分组名称
        return false;
    }
});

expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
    @Override
    public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
        // 处理子项点击事件
        ChildModel childModel = (ChildModel) adapter.getChild(groupPosition, childPosition);
        String childName = childModel.getChildName();
        // 这里可以添加自定义的逻辑,比如跳转到详情页面
        return false;
    }
});

在分组点击事件监听器中,我们可以获取点击的分组对象并进行相应的处理;在子项点击事件监听器中,我们可以获取点击的子项对象并进行相应的处理。

7. 高级应用:自定义视图

除了使用系统提供的简单布局,我们还可以自定义分组和子项的视图,以实现更丰富的界面效果。以下是一个自定义视图的示例:

自定义分组布局文件 group_item.xml

bash 复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="10dp">

    <ImageView
        android:id="@+id/group_icon"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:src="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/group_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:textSize="18sp" />
</LinearLayout>

自定义子项布局文件 child_item.xml

bash 复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="10dp">

    <ImageView
        android:id="@+id/child_icon"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:src="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/child_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:textSize="16sp" />
</LinearLayout>

修改适配器中的 getGroupView 和 getChildView 方法

bash 复制代码
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
    GroupModel groupModel = (GroupModel) getGroup(groupPosition);
    if (convertView == null) {
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.group_item, null);
    }
    ImageView groupIcon = convertView.findViewById(R.id.group_icon);
    TextView groupTextView = convertView.findViewById(R.id.group_name);
    groupTextView.setText(groupModel.getGroupName());
    return convertView;
}

@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
    ChildModel childModel = (ChildModel) getChild(groupPosition, childPosition);
    if (convertView == null) {
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.child_item, null);
    }
    ImageView childIcon = convertView.findViewById(R.id.child_icon);
    TextView childTextView = convertView.findViewById(R.id.child_name);
    childTextView.setText(childModel.getChildName());
    return convertView;
}

通过自定义视图,我们可以在分组和子项中添加更多的控件,如图片、按钮等,从而实现更复杂的界面效果。

8. 总结

ExpandableListView 是 Android 中一个非常实用的列表控件,它可以帮助我们实现具有分组功能的列表展示。通过本文的介绍,你应该已经掌握了 ExpandableListView 的基本用法,包括布局文件的添加、数据模型的创建、适配器的实现、点击事件的处理以及自定义视图的应用。在实际开发中,你可以根据具体需求对其进行进一步的扩展和优化,以满足不同的业务场景。希望本文对你有所帮助,祝你在 Android 开发中取得更好的成果!

相关推荐
还鮟1 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡3 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi003 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil4 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你4 小时前
Android View的绘制原理详解
android
移动开发者1号7 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号7 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best12 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk12 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭17 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin