(Tạp chí Lập trình) – Thành phần ListView mặc định được hỗ trợ trong Android SDK rất đơn giản khiến cho việc hiển thị dữ liệu đôi khi trở nên nhàm chán. Danh sách dữ liệu của bạn cần được trình bày một cách trực quan và lôi cuốn hơn. Ví dụ như danh sách các bài hát, hãy thử xem sự khác biệt:


Hôm nay mình sẽ cùng các bạn thảo luận về việc tùy biến lại ListView theo ý muốn bằng cách viết một ứng dụng nhỏ nhằm hiển thị danh sách các bài hát như các bạn đã thấy ở ảnh trên. Nếu bạn đã biết sử dụng ListView cơ bản và lấy dữ liệu từ nguồn xml (sử dụng SAX hoặc DOM) thì chúng ta cùng bắt đầu nào.

Nguồn dữ liệu XML

Trong thư mục assets tạo file listsong.xml chứa dữ liệu mà chúng ta sẽ sử dụng trong ứng dụng này:

[source lang=”xml”]
<?xml version="1.0" encoding="utf-8" ?>
<music>
<song>
<id>01</id>
<title>Someone Like You</title>
<singer>Adele</singer>
<duration>4:47</duration>
<icon>adele</icon>
</song>
<song>
<id>02</id>
<title>Stranger In Moscow</title>
<singer>Michael Jackson</singer>
<duration>5:44</duration>
<icon>mj</icon>
</song>
<song>
<id>03</id>
<title>Tìm Lại Bầu Trời</title>
<singer>Tuấn Hưng</singer>
<duration>5:28</duration>
<icon>tuanhung</icon>
</song>
<song>
<id>04</id>
<title>Chờ Ngày Mưa Tan</title>
<singer>Noo Phước Thịnh</singer>
<duration>3:32</duration>
<icon>phuocthinh</icon>
</song>
<song>
<id>05</id>
<title>Học Cách Đi Một Mình</title>
<singer>Lương Bích Hữu</singer>
<duration>3:43</duration>
<icon>luongbichhuu</icon>
</song>
<song>
<id>06</id>
<title>Chị Tôi</title>
<singer>Bằng Kiều</singer>
<duration>5:29</duration>
<icon>bangkieu</icon>
</song>
<song>
<id>07</id>
<title>Somebody’s Me</title>
<singer>Enrique Iglesias</singer>
<duration>3:58</duration>
<icon>iglesias</icon>
</song>
</music>

[/source]

Tùy biến giao diện mỗi mục của ListView

Thay vì chỉ là những chuỗi đơn giản chúng ta sẽ tạo ra một giao diện mới cho mỗi mục của ListView, chúng sẽ có một cấu trúc như thế này:


Trước khi tạo bố cục mới cho ListView, chúng ta sẽ tạo một cặp file để quy định màu nền cho các mục để tạo hiệu ứng mỗi khi nó được lựa chọn.

Màu nền mặc định.

Trong thư mục drawable tạo file gradient_bg.xml với nội dung như sau:

[source lang=”xml”]
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#D5DDE0"
android:centerColor="#e7e7e8"
android:endColor="#CFCFCF"
android:angle="270"
/>
</shape>

[/source]

Mục đích của file trên là nhằm tạo ra màu nền mặc định cho mỗi mục như chúng ta thấy ở ảnh dưới:


Màu nền khi được chọn.

Trong thư mục drawable tạo file gradient_bg_select.xml với nội dung như sau:

[source lang=”xml”]
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
>
<gradient
android:startColor="#78DDFF"
android:centerColor="#16cedb"
android:endColor="#09adb9"
android:angle="270"
/>

[/source]
gradient_bg_select.xml này sẽ giúp chúng ta tạo được màu nền cho các mục khi chúng được lựa chọn như ảnh dưới:


Vậy để làm sao có thể biết một mục có đang được chọn hay không? Cũng trong thư mục assets hãy tạo file list_selector.xml
với nội dung sau:

[source lang=”xml”]
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:state_selected="false"
android:state_pressed="false"
android:drawable="@drawable/gradient_bg"
/>
<item
android:state_selected="true"
android:state_pressed="false"
android:drawable="@drawable/gradient_bg_select"
/>
<item
android:state_pressed="true"
android:drawable="@drawable/gradient_bg_select"
/>
</selector>

[/source]

Giống như một file điều khiển, list_selector.xml sẽ dựa trên trạng thái của các mục như select (được chọn), press (đang được nhấn xuống) để giúp các mục có một hình nền như mong muốn. Nếu một mục không được chọn và cũng không được nhấn xuống thì hình nền sẽ ở trạng thái mặc định, ngược lại thì nó sẽ có hiệu ứng như bạn thấy ở trên.

Tạo giao diện cho các mục trong ListView

Trong thư mục res/layout/
tạo mới một file Android XML với tên list_row.xml:

[source lang=”xml”]
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="350dp"
android:layout_height="wrap_content"
android:background="@drawable/list_selector" >

<ImageView
android:id="@+id/list_image"
android:layout_width="50dip"
android:layout_height="50dip"
android:contentDescription="@string/app_name"
android:src="@drawable/tuanhung"
android:layout_marginRight="5dip"
android:padding="3dip"
/>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/list_image"
android:layout_toRightOf="@+id/list_image"
android:text="@string/songName"
android:textColor="#040404"
android:typeface="sans"
android:textSize="15sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/singer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:textColor="#343434"
android:textSize="12sp"
android:layout_marginTop="1dip"
android:layout_toRightOf="@+id/list_image"
android:text="@string/singer" />
<TextView
android:id="@+id/duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignTop="@id/title"
android:gravity="right"
android:text="@string/duration"
android:layout_marginRight="5dip"
android:textSize="12sp"
android:textColor="#10bcc9"
android:textStyle="bold"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/app_name"
android:src="@drawable/arrow"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"/>
</RelativeLayout>

[/source]

Các thành phần:

(list_image): Ảnh ca sĩ.
(
title) : Tên bài hát.
(
singer): Tên ca sĩ.
(
duration): Thời gian bài hát.


File bố cục chính (activity_main.xml):

[source lang=”xml”]
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<ListView
android:id="@+id/listView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:divider="#b5b5b5"
android:dividerHeight="1dp"
android:listSelector="@drawable/gradient_bg_select"
/>
</RelativeLayout>

[/source]

android:listSelector để giúp các mục có thể tham chiếu đến hiệu ứng màu nền cụ thể khi nó được chọn, màu nền này đã được định nghĩa trong file gradient_bg_select.xml.

android:divider
là màu đư
ờng kẻ phân cách giữa các mục.


Lấy dữ liệu từ nguồn xml và tùy chỉnh bố cục cho ListView

Tạo lớp Song với đầy đủ các thuộc tính để đại diện cho một bài hát:

[source lang=”java”]
public class Song {
private String id;
private String title;
private String singer;
private String duration;
private String icon;

public String getId(){
return id;
}

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

public String getTitle(){
return title;
}

public void setTitle(String title){
this.title = title;
}

public String getSinger(){
return singer;
}

public void setSinger(String singer){
this.singer = singer;
}

public String getDuration(){
return duration;
}

public void setDuration(String duration){
this.duration = duration;
}

public String getIcon(){
return icon;
}

public void setIcon(String icon){
this.icon = icon;
}
}

[/source]

Khi sử dụng ListView với bố cục mặc định thì chúng ta dùng ArrayAdapter để kết nối DataSource với ListView. Nhưng khi muốn tùy chỉnh lại bố cục cho ListView thì chúng ta cần tạo một lớp mới kế thừa từ lớp ArrayAdapter và viết chồng phương thức getView(). Sau đây là lớp MyArrayAdapter mà tôi đã tạo:

[source lang=”java”]
public class MyArrayAdapter extends ArrayAdapter<Song>{

Activity context = null;
int layoutId;
ArrayList<Song> arr = null;
//Contructor này dùng để lấy về những giá trị được truyền vào từ MainActivity
public MyArrayAdapter(Activity context, int layoutId, ArrayList<Song> list){
super(context, layoutId, list);
this.context = context;
this.layoutId = layoutId;
this.arr = list;
}

@Override
public View getView(int position, View convertView, ViewGroup parent){
/*
position: là vị trí của bàu hát trong list
convertView: dùng để lấy về các control của mỗi item
parent: chính là datasource được truyền vào từ MainActivity
*/
if(convertView==null){
LayoutInflater inflater = context.getLayoutInflater();
convertView = inflater.inflate(layoutId, null);
}
//Lấy về bài hát ở vị trí được yêu cầu
Song song = arr.get(position);
//Lấy ra những control được định nghĩa trong cấu trúc của mỗi item
ImageView icon = (ImageView)convertView.findViewById(R.id.list_image);
TextView title = (TextView)convertView.findViewById(R.id.title);
TextView singer = (TextView)convertView.findViewById(R.id.singer);
TextView duration = (TextView)convertView.findViewById(R.id.duration);

//Gán giá trị cho những control đó
title.setText(song.getTitle());
singer.setText(song.getSinger());
duration.setText(song.getDuration());

//Vì icon là ảnh nên ta phải lấy ra đường dẫn, dùng nó để lấy về image trong folder drawable
String uri_icon = "drawable/" + song.getIcon();
int ImageResoure = convertView.getContext().getResources().getIdentifier(uri_icon, null, convertView.getContext().getApplicationContext().getPackageName());
Drawable image = convertView.getContext().getResources().getDrawable(ImageResoure);
icon.setImageDrawable(image);
return convertView;
}
}

[/source]

Chúng ta đã tùy biến lại giao diện cho mỗi mục, đã có lớp MyArrayAdapter để kết nối DataSource. Việc cuối cùng là tải dữ liệu từ file xml lên. Mình sẽ dùng DOM để làm việc này thông qua lớp MainActivity.java:

[source lang=”java”]
public class MainActivity extends Activity {

Song song = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ArrayList<Song> arr = new ArrayList<Song>();
try{
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db= dbf.newDocumentBuilder();
Document doc = db.parse(getAssets().open("listsong.xml"));

//Lấy về node gốc của mỗi bài hát
NodeList nodeList = doc.getElementsByTagName("song");
for(int i=0; i<nodeList.getLength(); i++){
Node node = nodeList.item(i);
if(node.getNodeType() == Node.ELEMENT_NODE){
song = new Song();
Element elm = (Element)node;
//Id
NodeList idList = elm.getElementsByTagName("id");
Element idElement = (Element)idList.item(0);
song.setId(idElement.getTextContent().trim());

//title
NodeList titleList = elm.getElementsByTagName("title");
Element titleElement = (Element)titleList.item(0);
song.setTitle(titleElement.getTextContent().trim());

//singer
NodeList singerList = elm.getElementsByTagName("singer");
Element singerElement = (Element)singerList.item(0);
song.setSinger(singerElement.getTextContent().trim());

//duration
NodeList durationList = elm.getElementsByTagName("duration");
Element durationElement = (Element)durationList.item(0);
song.setDuration(durationElement.getTextContent().trim());

//icon
NodeList iconList = elm.getElementsByTagName("icon");
Element iconElement = (Element)iconList.item(0);
song.setIcon(iconElement.getTextContent().trim());
arr.add(song);
}
}
}catch(Exception e){

}
ListView lv = (ListView)findViewById(R.id.listView);
/*Thay vì việc sử dụng ArrayAdapter thì chúng ta sử dụng MyArrayAdapter
class chúng ta vừa kế thừa từ ArrayAdapter, và tham số thứ 2 là file list_row.xml
chúng ta đã tạo để custom lại layout cho listview
*/
MyArrayAdapter mayArr = new MyArrayAdapter(this, R.layout.list_row, arr);
lv.setAdapter(mayArr);
}
}

[/source]

Kết quả chúng ta có được:


Chú ý: Bạn cần thêm ảnh vào thư mục asset.

Mã nguồn bạn có thể tham khảo tại đây.

Đặng Phương Phương

Tham khảo:
http://www.codeproject.com/Articles/507651/Customized-Android-ListView-with-Image-and-Text