Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách tạo ListView đa cấp (ExpandableListView) trong Android

Giới thiệu

Bài viết hướng dẫn tạo danh sách đa cấp (multi-level list) sử dụng ExpandableListView - View gốc của Android hỗ trợ nhóm (group) và con (child) có thể mở/thu gọn.

Các bước thực hiện

Bước 1: Tạo dự án mới

Android Studio → File → New Project → Empty Activity.

Bước 2: Layout chính (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="4dp"
    tools:context=".MainActivity">

    <ExpandableListView
        android:id="@+id/expendableList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="@android:color/background_light"
        android:dividerHeight="0.5dp" />
</RelativeLayout>

Bước 3: Dữ liệu mẫu (ExpandableListData.java)

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

class ExpandableListData {
    static HashMap<String, List<String>> getData() {
        HashMap<String, List<String>> expandableListDetail = new HashMap<>();

        List<String> cricketPlayers = new ArrayList<>();
        cricketPlayers.add("MS.Dhoni");
        cricketPlayers.add("Sehwag");
        cricketPlayers.add("Shane Watson");
        cricketPlayers.add("Ricky Ponting");
        cricketPlayers.add("Shahid Afridi");

        List<String> footballPlayers = new ArrayList<>();
        footballPlayers.add("Cristiano Ronaldo");
        footballPlayers.add("Lionel Messi");
        footballPlayers.add("Gareth Bale");
        footballPlayers.add("Neymar JR");
        footballPlayers.add("David de Gea");

        List<String> tennisPlayers = new ArrayList<>();
        tennisPlayers.add("Roger Federer");
        tennisPlayers.add("Rafael Nadal");
        tennisPlayers.add("Andy Murray");
        tennisPlayers.add("Novak Jokovic");
        tennisPlayers.add("Sania Mirza");

        expandableListDetail.put("CẦU THỦ CRICKET", cricketPlayers);
        expandableListDetail.put("CẦU THỦ BÓNG ĐÁ", footballPlayers);
        expandableListDetail.put("CẦU THỦ TENNIS", tennisPlayers);

        return expandableListDetail;
    }
}

Bước 4: Custom Adapter (CustomExpandableListAdapter.java)

package app.com.sample;

import android.content.Context;
import android.graphics.Typeface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.TextView;

import java.util.HashMap;
import java.util.List;
import java.util.Objects;

class CustomExpandableListAdapter extends BaseExpandableListAdapter {
    private Context context;
    private List<String> expandableListTitle;
    private HashMap<String, List<String>> expandableListDetail;

    CustomExpandableListAdapter(Context context, List<String> expandableListTitle,
                                HashMap<String, List<String>> expandableListDetail) {
        this.context = context;
        this.expandableListTitle = expandableListTitle;
        this.expandableListDetail = expandableListDetail;
    }

    @Override
    public Object getChild(int listPosition, int expandedListPosition) {
        return Objects.requireNonNull(
                this.expandableListDetail.get(this.expandableListTitle.get(listPosition)))
                .get(expandedListPosition);
    }

    @Override
    public long getChildId(int listPosition, int expandedListPosition) {
        return expandedListPosition;
    }

    @Override
    public View getChildView(int listPosition, final int expandedListPosition,
                             boolean isLastChild, View convertView, ViewGroup parent) {
        final String expandedListText = (String) getChild(listPosition, expandedListPosition);
        if (convertView == null) {
            LayoutInflater layoutInflater = (LayoutInflater) this.context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = Objects.requireNonNull(layoutInflater).inflate(R.layout.list_row, null);
        }
        TextView textView = convertView.findViewById(R.id.listTitle);
        textView.setText(expandedListText);
        return convertView;
    }

    @Override
    public int getChildrenCount(int listPosition) {
        return this.expandableListDetail.get(this.expandableListTitle.get(listPosition)).size();
    }

    @Override
    public Object getGroup(int listPosition) {
        return this.expandableListTitle.get(listPosition);
    }

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

    @Override
    public long getGroupId(int listPosition) {
        return listPosition;
    }

    @Override
    public View getGroupView(int listPosition, boolean isExpanded,
                             View convertView, ViewGroup parent) {
        String listTitle = (String) getGroup(listPosition);
        if (convertView == null) {
            LayoutInflater layoutInflater = (LayoutInflater) this.context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = Objects.requireNonNull(layoutInflater).inflate(R.layout.list_row, null);
        }
        TextView listTitleTextView = convertView.findViewById(R.id.listTitle);
        listTitleTextView.setTypeface(null, Typeface.BOLD);
        listTitleTextView.setText(listTitle);
        return convertView;
    }

    @Override
    public boolean hasStableIds() {
        return false;
    }

    @Override
    public boolean isChildSelectable(int listPosition, int expandedListPosition) {
        return true;
    }
}

Bước 5: Layout item dòng (res/layout/list_row.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/listTitle"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:textColor="@android:color/black" />
</LinearLayout>

Bước 6: MainActivity.java

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.Toast;

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

public class MainActivity extends AppCompatActivity {
    ExpandableListView expandableListView;
    ExpandableListAdapter expandableListAdapter;
    List<String> expandableListTitle;
    HashMap<String, List<String>> expandableListDetail;

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

        expandableListView = findViewById(R.id.expendableList);
        expandableListDetail = ExpandableListData.getData();
        expandableListTitle = new ArrayList<>(expandableListDetail.keySet());
        expandableListAdapter = new CustomExpandableListAdapter(this, expandableListTitle, expandableListDetail);
        expandableListView.setAdapter(expandableListAdapter);

        // Sự kiện mở nhóm
        expandableListView.setOnGroupExpandListener(groupPosition -> {
            String title = expandableListTitle.get(groupPosition);
            Toast.makeText(getApplicationContext(), title + " Đã mở rộng", Toast.LENGTH_SHORT).show();
        });

        // Sự kiện thu gọn nhóm
        expandableListView.setOnGroupCollapseListener(groupPosition -> {
            String title = expandableListTitle.get(groupPosition);
            Toast.makeText(getApplicationContext(), title + " Đã thu gọn", Toast.LENGTH_SHORT).show();
        });

        // Sự kiện click item con
        expandableListView.setOnChildClickListener((parent, v, groupPosition, childPosition, id) -> {
            String group = expandableListTitle.get(groupPosition);
            String child = expandableListDetail.get(group).get(childPosition);
            Toast.makeText(getApplicationContext(), group + " -> " + child, Toast.LENGTH_SHORT).show();
            return false;
        });
    }
}

Bước 7: Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.com.sample">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Chạy ứng dụng

Kết nối thiết bị, nhấn Run. Click vào nhóm để mở/thu gọn, click item con để xem Toast.

Cách tạo ListView đa cấp (ExpandableListView) trong Android Cách tạo ListView đa cấp (ExpandableListView) trong Android

So sánh với RecyclerView (Hiện đại hơn)

ExpandableListView là View cũ, ít linh hoạt. Hiện nay dùng RecyclerView với các thư viện:

  • groupie - GroupieAdapter hỗ trợ section/header
  • Epoxy (Airbnb) - Type-safe, diffing tự động
  • ConcatAdapter (Jetpack) - Ghép nhiều adapter
  • Tự viết RecyclerView.Adapter với getItemViewType() cho group/child

Mở rộng: Nhiều cấp độ (Multi-level)

ExpandableListView chỉ hỗ trợ 2 cấp (Group → Child). Cần 3+ cấp:

  • Dùng RecyclerView + layout manager tùy chỉnh
  • Thư viện TreeViewRecyclerView hoặc AndroidTreeView
  • Hoặc thiết kế UI dạng drill-down (click group → mở Activity/Fragment mới)