Tổng quan về ExpandableListView
Lớp ExpandableListView
kế thừa từ ListViewListView
và thêm vào tính năng nhóm các item theo loại.
Ví dụ:
- Nhóm các sinh viên theo loại khá, giỏi, trung bình, yếu.
- Nhóm các nhóm chat theo từng chủ đề như học tập, ăn uống, giải trí, ...
Việc sử dụng ExpandableListView
cũng gần giống như ListView
nhưng có vài nét khác nhau ở việc tạo Adapter.
Kế thừa lớp BaseExpandableListAdapter
và override lại những phương thức như dưới đây:
public class CustomAdapter extends BaseExpandableListAdapter {
private static final String TAG = "CustomAdapter";
public CustomAdapter(Context context, HashMap<String, List<Student>> datas) {
// TODO something
}
@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;
}
}
Chú ý tới những phương thức chính sau:
getGroupCount
: trả về số phần tử của nhóm.getChildrenCount
: trả về số phần tử con ứng vớigroupPosition
.getGroup
: trả về đối tượng của header group, nghĩa là trả về phần tử tạigroupPosition
trong danh sách header group.getChild
: trả về đối tượng con trong nhóm, có vị trí là groupPosition
và con có vị trí làchildPosition
.getGroupView
: bản chất giốnggetView
trongListView
nhưnggetGroupView
trả vềView
hiển thị header group.getChildView
: bản chất giống nhưgetView
nhưng trả vềView
để hiển thịView
trong headerView
.
Có 1 CustomAdapter
như sau:
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 nhóm:
<?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 nhóm:
<?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
<?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>
Cài đặt data và setAdapter
cho ExpanableListView
trong MainActivity.java:
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 childrend
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 muốn các nhóm luôn expand thì làm như sau:
for(int i=0; i < adapter.getGroupCount(); i++)
eplStudent.expandGroup(i);
}
Dữ liệu có hơi khác biệt so với ListView
, cần dùng HashMap
có key kiểu String
ứng với header group, giá trị là 1 List<Student>
thể hiện các con của header group.
Lớp Student.java
package com.eitguide.nguyennghia.expandablelistviewandroid;
/**
* Created by nguyennghia on 8/29/16.
*/
public class Student {
private String name;
private float mediumScore;
public Student() {
// TODO something
}
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;
}
}
Những Event Listener sử dụng với ExpandableListView
Sự kiện khi nhóm được expanded
eplStudent.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
@Override
public void onGroupExpand(int groupPosition) {
Log.e(TAG, "onGroupExpand: " + groupPosition);
}
});
Sự kiện khi nhóm được collapse
eplStudent.setOnGroupCollapseListener(new ExpandableListView.OnGroupCollapseListener() {
@Override
public void onGroupCollapse(int groupPosition) {
Log.e(TAG, "onGroupCollapse: " + groupPosition);
}
});
Sự kiện khi nhấp chuột vào nhóm
eplStudent.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
Log.e(TAG, "onGroupClick: " + groupPosition);
return false;
}
});
Sự kiện khi nhấp chuột vào các con trong nhóm
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 phải trả return true
trong phương thức isChildSelectable
của CustomAdapter
.
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}