Trang chủ > Android > Lập trình Android – Expandable List View

Lập trình Android – Expandable List View


Chào mừng các bạn đến với bài đọc tiếp theo trong loạt bài hướng dẫn về lập trình Android. Các bạn thân mến, ở bài đọc trước, mình đã giới thiệu với các bạn cách để tạo một Custom Dialog. Hôm nay mình xin được tiếp tục giới thiệu với các bạn cách tạo một Custom UI Component khác được sử dụng khá phổ biến đó là ExpandableListView. Kết thúc bài đọc chúng ta sẽ có một UI Component như hình dưới:

Để có được một component như hình trên, chúng ta phải làm các thao tác sau:

Xây dựng layout cho component

Tạo 2 Java Beans đóng vai trò là group và child trong ExpandableListView

Tạo một CustomExpandableListAdapter

Tạo một Activity để gọi và hiển thị

1. Xây dựng layout cho component

Trước hết chúng ta tạo một layout trong đó có chứa thành phần ExpandableListView, trong thư mục layout, ta sửa lại file main.xml như sau:

<?xml version=“1.0” encoding=“utf-8”?>

<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android&#8221;

android:orientation=“vertical”

android:layout_width=“fill_parent”

android:layout_height=“fill_parent”>

<ExpandableListView android:id=“@+id/listView”

android:layout_width=“fill_parent”

android:layout_height=“fill_parent”

android:scrollbars=“none”/>

</LinearLayout>

Một ExpandableListView sẽ có các groups và mỗi group sẽ chứa các thành phần con là children, khi ta expand mỗi group, ta sẽ thấy các thành phần con nằm trong nó. Vì vậy để tùy biến một ExpandableListView, chúng ta sẽ phải tùy biến các groups và children của chúng. Chúng ta sẽ tạo layout cho group và child. Tạo mới 2 file trong thư mục layout:

group_layout.xml:

<?xml version=“1.0” encoding=“utf-8”?>

<LinearLayout

xmlns:android=http://schemas.android.com/apk/res/android&#8221;

android:orientation=“horizontal”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content”

android:background=“@drawable/header_background”>

<TextView android:id=“@+id/group_title”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content”

android:gravity=“center_vertical”

android:layout_marginLeft=“40dip”

android:paddingTop=“5dip”

android:textColor=“#000000”

android:textStyle=“bold”

android:textSize=“16sp”/>

</LinearLayout>

child_layout.xml:

<?xml version=“1.0” encoding=“utf-8”?>

<LinearLayout

xmlns:android=http://schemas.android.com/apk/res/android&#8221;

android:orientation=“horizontal”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content”

android:background=“#fa8400”>

<ImageView android:id=“@+id/child_icon”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_marginLeft=“30dip”/>

<TextView android:id=“@+id/child_title”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content”

android:gravity=“center_vertical”

android:textColor=“#ffffff”

android:textStyle=“bold”

android:textSize=“14sp”/>

</LinearLayout>

Như vậy chúng ta đã hoàn tất công việc tạo layout cho Custom ExpandableListView. Tiếp theo chúng ta sẽ tùy biến ExpandableListViewAdapter.

2. Tạo 2 Java Beans đóng vai trò là group và child trong ExpandableListView

Dưới đây là source code của 2 class Category và Item, Category đóng vai trò group và Item đóng vai trò là child trong ExpandableListView. Đối với các bạn mới đọc qua về Java chỉ để phục vụ cho mục đích lập trình Android thì có lẽ khái niệm java bean là cái gì đó có vẻ lạ lẫm. Tuy nhiên các bạn hoàn toàn không cần quan tâm tới khái niệm này. Java Bean thực chất chỉ là một java class bình thường với một constructor rỗng và các instance variable có access modifier là private cùng với các gettersetter cho chúng. Dưới đây là 2 java beans:

Category.java

public class Category {
private long id;
private String icon;

private String name;

public Category(){

}
public String getIcon() {
return icon;
}

public void setIcon(String icon) {
this.icon = icon;
}
public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

Item.java

public class Item {
private long cateId;

private long itemId;
private String name;

public Item(){
}

public long getCateId() {
return cateId;
}

public void setCateId(long cateId) {
this.cateId = cateId;
}

public long getItemId() {
return itemId;
}

public void setItemId(long itemId) {
this.itemId = itemId;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

3. Tạo ExpandableListAdapter

Như chúng ta đều biết, ExpandableListAdapter chính là Datasource của ExpandableListView. Nói cách khác mọi dữ liệu mà ExpandableListView hiển thị như icon, title, content… đều được lấy từ ExpandableListAdapter. Android cung cấp sẵn cho chúng ta một class có tên là BaseExpandableListAdapter, để tùy biến ExpandableListAdapter một cách dễ dàng, nhanh chóng và hiệu quả, chúng ta sẽ tạo một adapter extends từ class này. Dưới đây là toàn bộ code cho custom adapter mà ta cần tạo:

public class MyExpandableListAdapter extends BaseExpandableListAdapter {

private Context context;
private ArrayList<Category> groups; // an array list of categories
private ArrayList<ArrayList<Item>> children; // an array list of items

public MyExpandableListAdapter(Context context){
this.context = context;
}

public MyExpandableListAdapter(Context context, ArrayList<Category> groups,
ArrayList<ArrayList<Item>> children) {
this.context = context;
this.groups = groups;
this.children = children;
}

/**
* provide a method to add an item to a category dynamically, check if the
* category is existed otherwise throws an exception
*
* @param item
* @return void
*/
public void addItem(Item item) throws ClassNotFoundException {
// check if the category is existed
if (!this.findCategory(groups, item.getCateId())) {
throw new ClassNotFoundException(“Category doesn’t exist.”);
} else {
Category category = this.getCateInListById(groups, item.getCateId());
int index = groups.indexOf(category);
/*
* check if children list already contained the list to hold this
* group or else we have to create it
*/
if (children.size() < (index + 1)) {
children.add(new ArrayList<Item>());
}
children.get(index).add(item);
}
}

@Override
public Object getChild(int groupPosition, int childPosition) {
return children.get(groupPosition).get(childPosition);
}

@Override
public long getChildId(int groupPosition, int childPosition) {
Item item = (Item) children.get(groupPosition).get(childPosition);
return item.getItemId();
}

@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
Item item = (Item) getChild(groupPosition, childPosition);
Category category = (Category) getGroup(groupPosition);

if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.child_layout, null);
}

// set title for the item
TextView tv = (TextView) convertView.findViewById(R.id.child_title);
tv.setText(” ” + item.getName());

// set icon for the item, all item in the same category have the same
// icon
ImageView imv = (ImageView) convertView.findViewById(R.id.child_icon);
if ((category.getIcon() != null)
&& (category.getIcon().trim().length() > 0)) {
int iconUri = context.getResources().getIdentifier(
category.getIcon(), null, context.getPackageName());
Drawable drawable = context.getResources().getDrawable(iconUri);
imv.setBackgroundDrawable(drawable);
}
return convertView;
}

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

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

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

@Override
public long getGroupId(int groupPostion) {
return groups.get(groupPostion).getId();
}

@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
Category group = (Category) getGroup(groupPosition);
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.group_layout, null);
}
TextView tv = (TextView) convertView.findViewById(R.id.group_title);
tv.setText(” ” + group.getName());
return convertView;
}

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

@Override
public boolean isChildSelectable(int arg0, int arg1) {
return true;
}

/**
* search for a category through out an array list rely on its id
*
* @param arraylist
* @param cateId
* @return true if found the category otherwise false
*/
private boolean findCategory(ArrayList<Category> arraylist, long cateId) {
for (Category category : arraylist) {
if (cateId == category.getId()) {
return true;
}
}
return false;
}
/**
* method to get a category from a group depends on its id
* @param arraylist
* @param cateId
* @return category with the specific id if it exists otherwise return null
*/

private Category getCateInListById(ArrayList<Category> arraylist, long cateId){
for (Category category : arraylist) {
if (cateId == category.getId()) {
return category;
}
}
return null;
}
}

Có lẽ không cần phải giải thích gì nhiều về source code, bản thân tên của các phương thức, tên biến và các tham số đã tự nó nói lên những điều cần nói. Có một chú ý nhỏ là ở đây mình tạo ra phương thức addItem() với mục đích cung cấp phương pháp add động trực tiếp một child vào một group nào đó của ExpandableListView.  Trong phần Activity gọi và hiển thị, mình sẽ lấy một ví dụ về nó. Đối với các phương thức getGroupId, getChildId, mình trả về id của Category và Item tương ứng do đó khi ta click lên mỗi group hoặc item chúng ta có thể lấy được ngay id của Category hay Item thay vì groupPosition hay childPosition.

4. Tạo Activity để gọi và hiển thị ExpandableListView

Cuối cùng chúng ta chỉ việc tạo một Activity, trong Activity này chúng ta tạo ra một số dữ liệu giả lập cho Custom ExpandableAdapter, bind dữ liệu đó lên ExpandableListView và hiển thị lên màn hình. Dưới đây là source code cho Activity:
public class ExpandableListViewActivity extends Activity {

// Declare MyExpandableListAdapter, datasource of expandable list view
MyExpandableListAdapter adapter;
public final Context THIS = this;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

ExpandableListView listView = (ExpandableListView) findViewById(R.id.listView);

listView.setOnChildClickListener(new OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View view,
int groupPosition, int childPosition, long id) {
Toast.makeText(THIS,
“I am child with id = ” + id, Toast.LENGTH_SHORT).show();
return false;
}
});

listView.setOnGroupClickListener(new OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView parent, View view,
int groupPosition, long id) {
// Toast.makeText(THIS,
// “I am group with id = ” + id, Toast.LENGTH_SHORT).show();
return false;
}
});

// create datasource
fakeData();

// bind data to list view
listView.setAdapter(adapter);
}

/*
* Here we manually create some fake categories and fake items to bind into the
* list view. In real world we should never do it this way!?
*/
public void fakeData(){

ArrayList groups = new ArrayList();

for (int i = 1; i <= 4; i++) {
Category category = new Category();
switch (i) {
case 1:
category.setId(i);
category.setIcon(“drawable/bear”);
category.setName(“Bears”);
break;
case 2:
category.setId(i);
category.setIcon(“drawable/bee”);
category.setName(“Bees”);
break;
case 3:
category.setId(i);
category.setIcon(“drawable/dog”);
category.setName(“Dogs”);
break;
case 4:
category.setId(i);
category.setIcon(“drawable/monkey”);
category.setName(“Monkeys”);
break;
}
groups.add(category);
}

ArrayList<ArrayList> children = new ArrayList<ArrayList>();
ArrayList itemList;
for (int i = 0; i < 4; i++) {
itemList = new ArrayList();
for (int j = 1; j <= 5; j++) {
switch (i) {
case 0:
// add the items to Bears category
Item item1 = new Item();
item1.setCateId(1);
item1.setItemId(j);
item1.setName(“I am Bears No. ” + j);
itemList.add(item1);
break;
case 1:
// add items Bees category
Item item2 = new Item();
item2.setCateId(2);
item2.setItemId(j+5);
item2.setName(“I am Bees No. ” + j);
itemList.add(item2);
break;
case 2:
// add items to Dogs category
Item item3 = new Item();
item3.setCateId(3);
item3.setItemId(j+10);
item3.setName(“I am Dogs No. ” + j);
itemList.add(item3);
break;
case 3:
// add items to Monkeys category
Item item4 = new Item();
item4.setCateId(4);
item4.setItemId(j+15);
item4.setName(“I am Monkey No. ” + j);
itemList.add(item4);
break;
}
}
children.add(itemList);
}

adapter = new MyExpandableListAdapter(THIS,
groups, children);

// create new Item and add it directly to the adapter with the help of
// method addItem that we have created to test it
try {
Item item = new Item();
item.setCateId(1);// add it to Bears Category
item.setItemId(21);// hardcode the id, we should never do it in real world!?
item.setName(“I am newly added bear”);
adapter.addItem(item);
} catch (ClassNotFoundException ex) {
Log.v(“ADD NEW ITEM”, ex.toString());
}

}
}
Như vậy là chúng đã tùy biến xong một UI Component trong Android. Từ bài đọc này, các bạn có thể thay đổi ý tưởng để tự tao ra cho riêng mình những ExpandableListView thích hợp nhất cho từng ứng dụng cụ thể. Mình hy vọng chúng ta sẽ cùng nhau trao đổi những gì mà các bạn còn cảm thấy băn khoăn trong phần comment. Đây là toàn bộ phần source code cho ứng dụng trên, các bạn có thể download và chạy thử.

Chuyên mục:Android
  1. bravehoang
    15/08/2012 lúc 10:53 sáng

    Hì, bạn up giùm lại link đi bạn. Bài hướng dẫn rất chi tiết. I like it.

    • 16/08/2012 lúc 2:21 sáng

      Bạn chịu khó code lại theo hướng dẫn trong bài. Mình bị mất máy tính nên không còn source code. Ngại viết lại :))

  2. housemouse
    16/06/2012 lúc 2:53 sáng

    link down đã die, bạn up lại được không?

    • 01/02/2013 lúc 5:32 chiều

      Mình mất máy tính, mất hết dữ liệu.😦

  3. fantomas
    09/06/2012 lúc 4:27 sáng

    Mình mới tìm hiểu android, cho mình hỏi cách để set icon trong drawble cho item trong listview là như thế nào vậy bạn ?

    • 21/07/2012 lúc 10:45 chiều

      Mình không biết là bạn hỏi set icon cho ListView hay ExpandableListview. Dưới đây là cách set icon cho ExpandableListView:

      listView.setGroupIndicator(getResources().getDrawable(R.drawable.group_icon));
      listView.setChildIndicator(getResources().getDrawable(R.drawable.child_icon));

      Đối với ListView, bạn nên tạo một file .xml để hiển thị ListView row theo ý muốn của bạn.

  4. Minh Tam
    12/05/2012 lúc 2:52 sáng

    Minh thay bai viet rat hay. Ban co the cho minh mail de tien lam quen voi ban duoc ko ?
    Chan thanh cam on ban

    • 12/05/2012 lúc 7:56 sáng

      Cám ơn bạn đã quan tâm tới blog này. Nếu bạn cần trao đổi gì thì cứ comment lên đây để tất cả mọi người cùng thảo luận. Thanks.

  1. No trackbacks yet.

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s

%d bloggers like this: