Giới thiệu
Sau khi hoàn thành các bài viết về ListView tôi chợt nhận ra rằng còn một View khác cũng có chức năng giống như ListView đó là ExpandableListView. ExpandableListView là loại ListView giúp chúng ta có thể gom nhóm các view con thành một group. Chúng ta có thể expand của hay collapse phần header để xem thông tin các thành phần con trong header này. Chi tiết về các sử dụng Expandable sẽ được giới thiệu ngay dưới đây.
Tổng quan về ExpandableListView
Thực chất thì class ExpandableListView là một ListView. ExpandableListView kế thừa từ ListViewListView và thêm vào tính năng đó là group các item theo một loại nào đó.
Ví dụ
- Group các sinh viên theo loại khá, giỏi, trung bình, yếu.
- Group các nhóm chat theo chủ đề: Chat về học tập, ăn uống, giải trí .v.v.
Nếu bạn nào chưa có kiến thức về ListView thì hãy xem lại ba bài viết về ListView của tôi ở dưới đây:
ListView trong Android Phần 1
ListView trong Android (Phần 2)
ListView Multiple Selection Và ListView Single Selection
Trước khi bắt đầu tìm hiểu thì bạn hãy xem qua video Demo ExpandableListView
Việc sử dụng ExpandableListView cũng khá giống như ListView nhưng có vài nét khác nhau ở việc tạo Adapter cho ExpandableListView.
Chúng ta phải extends class BaseExpandableaseListAdapter và override lại những phương thức như dứoi đây:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
public class CustomAdapter extends BaseExpandableListAdapter { private static final String TAG = "CustomAdapter"; public CustomAdapter(Context context, HashMap<String, List<Student>> datas) { } @Override public int getGroupCount() { return 0; } @Override public int getChildrenCount(int groupPosition) { return 0; } @Override public Object getGroup(int groupPosition) { return null; } @Override public Object getChild(int groupPosition, int childPosition) { return null; } @Override public long getGroupId(int groupPosition) { return 0; } @Override public long getChildId(int groupPosition, int childPosition) { return 0; } @Override public boolean hasStableIds() { return false; } @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { return null; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { return null; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return false; } } |
Các bạn cần chú ý tới những phương thức chính sau:
- getGroupCount: Trả về số phần tử của group.
- getChildrenCount: Trả về số phần tử con ứng với groupPosition.
- getGroup: Trả về object của header group. Có nghĩa là trả về phần tử tại groupPosition trong danh sách header group.
- getChild: Trả về object child trong Group có vị trí là groupPostion và child có vị trí là childPosition.
- getGroupView: bản chất giống getView trong ListView. Nhưng getGroupView sẽ trả về View hiển thị Group Header.
- getChildView: Bản chất cũng giống như getView. Nhưng ở đây getChildView trả về View để hiển thị view trong Header View.
Để hình dung rõ hơn những điều tôi vừa giải thích ở trên tôi có một custom Adapter như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
package com.eitguide.nguyennghia.expandablelistviewandroid; 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.HashMap; import java.util.List; /** * Created by nguyennghia on 8/29/16. */ public class CustomAdapter extends BaseExpandableListAdapter { private static final String TAG = "CustomAdapter"; private Context mContext; private List<String> mHeaderGroup; private HashMap<String, List<Student>> mDataChild; public CustomAdapter(Context context, List<String> headerGroup, HashMap<String, List<Student>> datas) { mContext = context; mHeaderGroup = headerGroup; mDataChild = datas; } @Override public int getGroupCount() { return mHeaderGroup.size(); } @Override public int getChildrenCount(int groupPosition) { return mDataChild.get(mHeaderGroup.get(groupPosition)).size(); } @Override public Object getGroup(int groupPosition) { return mHeaderGroup.get(groupPosition); } @Override public Object getChild(int groupPosition, int childPosition) { return mDataChild.get(mHeaderGroup.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) { if (convertView == null) { LayoutInflater li = LayoutInflater.from(mContext); convertView = li.inflate(R.layout.group_layout, parent, false); } TextView tvHeader = (TextView) convertView.findViewById(R.id.tv_header); tvHeader.setText(mHeaderGroup.get(groupPosition)); return convertView; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { if (convertView == null) { LayoutInflater li = LayoutInflater.from(mContext); convertView = li.inflate(R.layout.student_row_layout, parent, false); } TextView tvStudentName = (TextView) convertView.findViewById(R.id.tv_student_name); TextView tvStudentMediumScore = (TextView) convertView.findViewById(R.id.tv_student_medium_score); tvStudentName.setText(((Student) getChild(groupPosition, childPosition)).getName()); tvStudentMediumScore.setText(String.valueOf(((Student) getChild(groupPosition, childPosition)).getMediumScore())); return convertView; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return false; } } |
Với group_layout.xml để hiển thị UI của group
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ecf0f1" android:padding="5dp"> <TextView android:textColor="#16a085" android:id="@+id/tv_header" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="24dp" /> </FrameLayout> |
Và student_row_layout.xml biểu diễn UI của view con trong group
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="3dp" android:background="#ecf0f1" android:orientation="vertical" android:padding="5dp"> <TextView android:id="@+id/tv_student_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="#34495e" android:textSize="18dp" /> <TextView android:id="@+id/tv_student_medium_score" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="#7f8c8d" android:textSize="16dp" /> </LinearLayout> |
Thêm ExpandableListView trong main_activity.xml
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.eitguide.nguyennghia.expandablelistviewandroid.MainActivity"> <ExpandableListView android:id="@+id/eplChat" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> |
Setup data và setAdapter cho ExpanableListView trong MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
package com.eitguide.nguyennghia.expandablelistviewandroid; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.DisplayMetrics; import android.util.Log; import android.view.View; import android.widget.ExpandableListView; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private ExpandableListView eplStudent; private HashMap<String, List<Student>> mData; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); eplStudent = (ExpandableListView) findViewById(R.id.eplChat); DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); int width = metrics.widthPixels; eplStudent.setIndicatorBounds(width - dp2px(50), width - dp2px(10)); //prepare data //data for header group final List<String> listHeader = new ArrayList<>(); listHeader.add("Xuất sắc"); listHeader.add("Giỏi"); listHeader.add("Khá"); listHeader.add("Trung Bình"); listHeader.add("Yếu"); //data for child mData = new HashMap<>(); List<Student> listStudentXs = new ArrayList<>(); listStudentXs.add(new Student("Tran Phuc", 9.8f)); listStudentXs.add(new Student("Pham Phu", 9.9f)); List<Student> listStudentGioi = new ArrayList<>(); listStudentGioi.add(new Student("Nguyen Nghia", 8.8f)); listStudentGioi.add(new Student("Nguyen Minh", 8.0f)); List<Student> listStudentKha = new ArrayList<>(); listStudentKha.add(new Student("Nguyen Tien", 7.9f)); listStudentKha.add(new Student("Hoang Son", 7.6f)); listStudentKha.add(new Student("Tran Tien", 7.9f)); listStudentKha.add(new Student("Hai Trieu", 7.5f)); List<Student> listStudentTrungBinh = new ArrayList<>(); listStudentTrungBinh.add(new Student("Nguyen Ngoc", 5.9f)); listStudentTrungBinh.add(new Student("Hoang Nam", 6.0f)); listStudentTrungBinh.add(new Student("Tran Anh", 6.4f)); List<Student> listStudentYeu = new ArrayList<>(); listStudentYeu.add(new Student("Nguyen Mai", 4.9f)); listStudentYeu.add(new Student("Tran Hai", 5.5f)); listStudentYeu.add(new Student("Duong Phong", 5.1f)); mData.put(listHeader.get(0), listStudentXs); mData.put(listHeader.get(1), listStudentGioi); mData.put(listHeader.get(2), listStudentKha); mData.put(listHeader.get(3), listStudentTrungBinh); mData.put(listHeader.get(4), listStudentYeu); //setup adapter for ExpandableListView CustomAdapter adapter = new CustomAdapter(this, listHeader, mData); eplStudent.setAdapter(adapter); eplStudent.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { Log.e(TAG, "onChildClick: " + listHeader.get(groupPosition) + ", " + mData.get(listHeader.get(groupPosition)).get(childPosition).getName()); return false; } }); eplStudent.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() { @Override public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { Log.e(TAG, "onGroupClick: " + groupPosition); return false; } }); eplStudent.setOnGroupCollapseListener(new ExpandableListView.OnGroupCollapseListener() { @Override public void onGroupCollapse(int groupPosition) { Log.e(TAG, "onGroupCollapse: " + groupPosition); } }); eplStudent.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() { @Override public void onGroupExpand(int groupPosition) { Log.e(TAG, "onGroupExpand: " + groupPosition); } }); } public int dp2px(float dp) { // Get the screen's density scale final float density = getResources().getDisplayMetrics().density; // Convert the dps to pixels, based on density scale return (int) (dp * density + 0.5f); } } |
Nếu các bạn muốn các group luôn luôn expand thì có thể làm như sau:
1 2 3 |
for(int i=0; i < adapter.getGroupCount(); i++) eplStudent.expandGroup(i); } |
Chúng ta thấy rằng data của chúng ta có hơi khác biệt so với ListView. Ở đây chúng ta sẽ dùng HashMap có Key kiểu String ứng với header group và value và một List<Student> thể hiện các con của header group. Cũng dễ hiểu phải không nào.
Class Student.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package com.eitguide.nguyennghia.expandablelistviewandroid; /** * Created by nguyennghia on 8/29/16. */ public class Student { private String name; private float mediumScore; public Student() { } public Student(String name, float mediumScore) { this.name = name; this.mediumScore = mediumScore; } public String getName() { return name; } public void setName(String name) { this.name = name; } public float getMediumScore() { return mediumScore; } public void setMediumScore(float mediumScore) { this.mediumScore = mediumScore; } } |
Source code ExpandableListViewAndroid
Những Event Listener sử dụng với ExpandableListView
Event khi Group được Expanded
1 2 3 4 5 6 |
eplStudent.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() { @Override public void onGroupExpand(int groupPosition) { Log.e(TAG, "onGroupExpand: " + groupPosition); } }); |
Event khi Group được Colapsed
1 2 3 4 5 6 |
eplStudent.setOnGroupCollapseListener(new ExpandableListView.OnGroupCollapseListener() { @Override public void onGroupCollapse(int groupPosition) { Log.e(TAG, "onGroupCollapse: " + groupPosition); } }); |
Event khi click và Group
1 2 3 4 5 6 7 |
eplStudent.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() { @Override public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { Log.e(TAG, "onGroupClick: " + groupPosition); return false; } }); |
Event khi click vào các child trong group.
1 2 3 4 5 6 7 |
eplStudent.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { Log.e(TAG, "onChildClick: " + listHeader.get(groupPosition) + ", " + mData.get(listHeader.get(groupPosition)).get(childPosition).getName()); return false; } }); |
Tuy nhiên để event này xảy ra bạn phải return true trong phương thức isChildSelectable của CustomAdapter.
1 2 3 4 |
@Override public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } |
Kết luận
Vậy là chúng ta đã đi xong một bài về ListView nữa rồi. Đến bây giời tôi tin rằng các UI về danh sách các bạn có thể design và custom một cách dễ dàng. Nếu có bất cứ thắc mắc nào trong bài viết vui lòng để lại bình luận ở phía dưới hoặc liên hệ qua fanpage Eitguide Android để được giải đáp sớm nhất.
cảm ơn bài viết của bạn rất nhiều nhé, hy vọng bạn ra được nhiều bài hướng dẫn hay và thu vị hơn nữa, chân thành cảm ơn bạn
nếu muốn làm expandablelistview bằng cách bóc tách từ html sao bạn. Ví dụ đã đổ dữ liệu ra html có sẵn rồi giờ muốn chuyển sang app nhưng dữ liệu có nhiều nếu làm từng cái như vậy thì lâu lắm
//data for child
mình muốn thêm nhiều danh sách như listStudentKem,.. vào db rồi tự động lấy ra thì phải lm như nào ạ ?