CyStack logo
  • Sản phẩm & Dịch vụ
  • Giải pháp
  • Bảng giá
  • Công ty
  • Tài liệu
Vi

vi

Mục lục

Trang chủBlogVí dụ về Android Expandab...
Android

Ví dụ về Android ExpandableListView: Khám phá, cấu trúc và triển khai

6 phút đọc23/10/2025
CyStack Author
Chris Pham

Technical Writer

0 lượt xem
Reading Time: 6 minutes

Trong bài blog này, chúng ta sẽ cùng nhau đi sâu vào cách triển khai một ExpandableListView từ đầu đến cuối, hiểu rõ cấu trúc và code mẫu để bạn có thể tự tin áp dụng ngay vào dự án của mình. Chúng ta sẽ xây dựng một ví dụ thực tế, từ việc thiết lập layout, tạo dữ liệu mẫu cho đến việc viết Adapter tùy chỉnh và xử lý các sự kiện tương tác.

Khám phá Android ExpandableListView

ExpandableListView trong Android là một View đặc biệt, cho phép hiển thị các mục trong một danh sách hai cấp cuộn theo chiều dọc. Nó khác biệt với ListView thông thường ở chỗ nó hỗ trợ hai cấp độ: các nhóm mà người dùng có thể dễ dàng mở rộng và thu gọn để xem, và các mục con tương ứng bên trong mỗi nhóm.

Để tải dữ liệu vào các mục liên quan đến ExpandableListView, chúng ta sử dụng ExpandableListAdapter. Dưới đây là một số phương thức quan trọng mà lớp Adapter này thường sử dụng:

  • setChildIndicator(Drawable): Chúng ta dùng phương thức này để hiển thị một indicator bên cạnh mỗi mục con, thể hiện trạng thái hiện tại của nó. Nếu một mục con là mục cuối cùng trong một nhóm, trạng thái state_last sẽ được thiết lập.
  • setGroupIndicator(Drawable): Một indicator sẽ được vẽ bên cạnh mỗi nhóm để biểu thị trạng thái của nhóm đó – mở rộng (expanded) hoặc thu gọn (collapsed). Nếu nhóm rỗng, trạng thái state_empty được thiết lập. Nếu nhóm đang mở rộng, trạng thái state_expanded được thiết lập.
  • getGroupView(): Phương thức này trả về View cho tiêu đề nhóm (header của nhóm).
  • getChildView(): Phương thức này trả về View cho từng mục con riêng lẻ.

Chúng ta cũng sẽ làm việc với các interface đáng chú ý sau, được ExpandableListView sử dụng để xử lý các sự kiện tương tác:

  • ExpandableListView.OnChildClickListener: Chúng ta override interface này để triển khai phương thức callback được gọi khi một mục con trong danh sách được mở rộng và nhấp vào.
  • ExpandableListView.OnGroupClickListener: Chúng ta override interface này để triển khai phương thức callback được gọi khi tiêu đề của một nhóm trong danh sách được mở rộng và nhấp vào.
  • ExpandableListView.OnGroupCollapseListener: Chúng ta sử dụng interface này để nhận thông báo khi một nhóm được thu gọn.
  • ExpandableListView.OnGroupExpandListener: Chúng ta sử dụng interface này để nhận thông báo khi một nhóm được mở rộng.

Cấu trúc dự án của Android ExpandableListView

Dự án mẫu mà chúng ta sẽ xây dựng bao gồm ba lớp chính, mỗi lớp đóng một vai trò quan trọng trong việc triển khai ExpandableListView:

  • MainActivity: Lớp này chịu trách nhiệm hiển thị layout chính, chứa ExpandableListView và thiết lập các sự kiện tương tác.
  • ExpandableListDataPump: Lớp này đóng vai trò là nguồn dữ liệu. Chúng ta sẽ tạo một tập hợp dữ liệu ngẫu nhiên trong một List và ánh xạ các mục con vào các tiêu đề nhóm tương ứng bằng cách sử dụng HashMap.
  • CustomExpandableListAdapter: Lớp này là cầu nối giữa dữ liệu và ExpandableListView. Nó cung cấp cho MainActivity dữ liệu từ lớp ExpandableListDataPump để hiển thị lên giao diện.

Triển khai Code cho Android ExpandableListView

Layout activity_main.xml của chúng ta sẽ chỉ bao gồm một ExpandableListView nằm trong một RelativeLayout.



    



Thuộc tính android:indicatorLeft đặt giới hạn bên trái cho indicator của các mục. Một lưu ý quan trọng là chúng ta không thể sử dụng giá trị wrap_content cho thuộc tính android:layout_height của ExpandableListView trong XML trừ khi kích thước của parent đã được chỉ định rõ ràng.

Layout group định nghĩa giao diện cho tiêu đề của mỗi nhóm trong ExpandableListView. Chúng ta chỉ cần một TextView để hiển thị tên nhóm.



    


Layout row định nghĩa giao diện cho từng mục con riêng lẻ trong mỗi nhóm. Tương tự, chúng ta cũng sử dụng một TextView để hiển thị nội dung của mục con.



    


Lớp ExpandableListDataPump chịu trách nhiệm tạo ra dữ liệu mẫu mà chúng ta sẽ hiển thị trên ExpandableListView. Chúng ta sử dụng một HashMap để ánh xạ các tiêu đề nhóm tới một List các mục con (List).

package com.journaldev.expandablelistview;

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

public class ExpandableListDataPump {
    public static HashMap> getData() {
        HashMap> expandableListDetail = new HashMap>();

        List cricket = new ArrayList();
        cricket.add("India");
        cricket.add("Pakistan");
        cricket.add("Australia");
        cricket.add("England");
        cricket.add("South Africa");

        List football = new ArrayList();
        football.add("Brazil");
        football.add("Spain");
        football.add("Germany");
        football.add("Netherlands");
        football.add("Italy");

        List basketball = new ArrayList();
        basketball.add("United States");
        basketball.add("Spain");
        basketball.add("Argentina");
        basketball.add("France");
        basketball.add("Russia");

        expandableListDetail.put("CRICKET TEAMS", cricket);
        expandableListDetail.put("FOOTBALL TEAMS", football);
        expandableListDetail.put("BASKETBALL TEAMS", basketball);
        return expandableListDetail;
    }
}

Trong đoạn code trên, đối tượng expandableListDetail chịu trách nhiệm ánh xạ các chuỗi tiêu đề nhóm tới các mục con tương ứng của chúng bằng cách sử dụng một ArrayList các chuỗi.

package com.journaldev.expandablelistview;

import java.util.HashMap;
import java.util.List;
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;

public class CustomExpandableListAdapter extends BaseExpandableListAdapter {

    private Context context;
    private List expandableListTitle;
    private HashMap> expandableListDetail;

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

    @Override
    public Object getChild(int listPosition, int expandedListPosition) {
        return 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 = layoutInflater.inflate(R.layout.list_item, null);
        }
        TextView expandedListTextView = (TextView) convertView
                .findViewById(R.id.expandedListItem);
        expandedListTextView.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 = layoutInflater.inflate(R.layout.list_group, null);
        }
        TextView listTitleTextView = (TextView) 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;
    }
}

Lớp này extends BaseExpandableListAdapter và override các phương thức trong lớp cơ sở để cung cấp View cho ExpandableListView. Các phương thức như getGroupView()getChildView() có nhiệm vụ inflate layout tương ứng (list_group.xml hoặc list_item.xml) và điền dữ liệu vào View đó

Cuối cùng, lớp MainActivity sẽ tổng hợp tất cả các thành phần lại với nhau, khởi tạo ExpandableListView, thiết lập Adapter và xử lý các sự kiện click.

package com.journaldev.expandablelistview;

import android.support.v7.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 expandableListTitle;
    HashMap> expandableListDetail;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        expandableListView = (ExpandableListView) findViewById(R.id.expandableListView);
        expandableListDetail = ExpandableListDataPump.getData();
        expandableListTitle = new ArrayList(expandableListDetail.keySet());
        expandableListAdapter = new CustomExpandableListAdapter(this, expandableListTitle, expandableListDetail);
        expandableListView.setAdapter(expandableListAdapter);

        expandableListView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
            @Override
            public void onGroupExpand(int groupPosition) {
                Toast.makeText(getApplicationContext(),
                        expandableListTitle.get(groupPosition) + " List Expanded.",
                        Toast.LENGTH_SHORT).show();
            }
        });

        expandableListView.setOnGroupCollapseListener(new ExpandableListView.OnGroupCollapseListener() {
            @Override
            public void onGroupCollapse(int groupPosition) {
                Toast.makeText(getApplicationContext(),
                        expandableListTitle.get(groupPosition) + " List Collapsed.",
                        Toast.LENGTH_SHORT).show();
            }
        });

        expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
            @Override
            public boolean onChildClick(ExpandableListView parent, View v,
                                        int groupPosition, int childPosition, long id) {
                Toast.makeText(
                        getApplicationContext(),
                        expandableListTitle.get(groupPosition)
                                + " -> "
                                + expandableListDetail.get(
                                expandableListTitle.get(groupPosition)).get(
                                childPosition), Toast.LENGTH_SHORT
                ).show();
                return false;
            }
        });
    }
}

Trong đoạn code trên, chúng ta đã triển khai tất cả các interface đã được thảo luận ở phần trước. Để đơn giản hóa, chúng ta chỉ hiển thị một Toast với tên của mục hoặc trạng thái của nhóm cho mỗi lần nhấp. Tuy nhiên, bạn có thể dễ dàng sửa đổi các phương thức callback này để thực hiện bất kỳ hoạt động nào khác mà ứng dụng của bạn yêu cầu, ví dụ như mở một Activity mới, cập nhật dữ liệu, hoặc tương tác với Fragment.

Lưu ý: ExpandableListView có khả năng cuộn theo mặc định. Điều này kết thúc phần hướng dẫn chi tiết về code ExpandableListView trong Android.

Kết luận

Qua bài blog này, chúng ta đã cùng nhau khám phá ExpandableListView – một View mạnh mẽ giúp chúng ta tổ chức và hiển thị dữ liệu phân cấp một cách trực quan và dễ sử dụng trong các ứng dụng Android. Chúng ta đã tìm hiểu cách nó hoạt động dựa trên cấu trúc groupchildren, cùng với vai trò trung tâm của ExpandableListAdapter trong việc cung cấp dữ liệu. Hãy mạnh dạn áp dụng ExpandableListView vào các dự án của bạn để nâng cao trải nghiệm người dùng, giúp họ tiếp cận thông tin một cách nhanh chóng và hiệu quả nhất.

Về tác giả

Chris Pham
Chris PhamTechnical Writer

I have over 5 years of experience writing technical documentation for tech products, making them accessible and user-friendly. My focus is always on providing clear and precise information. @#@ Tôi đã có hơn 5 năm kinh nghiệm viết tài liệu kỹ thuật cho các sản phẩm công nghệ, giúp người dùng dễ dàng tiếp cận và sử dụng. Tôi luôn tập trung vào việc cung cấp thông tin chính xác và dễ hiểu.

Cập nhật thông tin mới nhấtNhận các thông tin mới nhất về mối đe dọa, báo cáo an ninh mạng từ CyStack về hòm thư điện tử của bạn

Thảo luận (0)

Đăng nhập để thảo luận

Bài viết liên quan