Nếu bạn nào muốn rèn luyện thêm lập trình Java, lập trình Android, lập trình Webservice với tổng thời lượng học >80 giờ thì có thể đăng ký học theo các link sau:
1) Lập trình java trong 4 tuần – 19 giờ(chỉ dành cho những ai CHƯA BIẾT GÌ VỀ LẬP TRÌNH hoặc đã biết lơ mơ về Java, lý thuyết và các bài tập phong phú tạo nền tảng lập trình Android và các hướng khác liên quan tới Java): |
[polldaddy poll=9764234]
Ở bài tập 13 bạn đã được thực hành với ListView control. Trong bài tập này bạn sẽ học cách Custom lại layout cho ListView trong ứng dụng Android của bạn. Tôi nghĩ bài tập này nó rất quan trọng và thực tế bởi vì trong các ứng dụng Android có liên quan tới ListView thì đa phần chúng ta phải custom lại cho đúng với yêu cầu của khách hàng.
Tôi có làm một ví dụ về quản lý nhân viên với giao diện bên dưới đây:
– Bạn quan sát là phần danh sách nhân viên bên dưới là Custom Layout.
– Mỗi dòng trong ListView sẽ có 3 đối tượng: ImageView, TextView và Checkbox.
– Khi nhập nhân viên nếu người sử dụng chọn Nữ thì sẽ hiển thị hình là con gái, nếu chọn Nam thì hiển thị hình là con trai (bạn nhìn danh sách hình nhỏ nhỏ 16×16 ở ListView).
– Mã và tên của nhân viên sẽ được hiển thị vào TextView
– Checkbox cho phép người sử dụng checked (nhằm đánh dấu những nhân viên muốn xóa, ở đây cho phép xóa nhiều nhân viên)
– Bạn để ý Tôi có thêm 1 ImageButton có hình màu Chéo đỏ, nó dùng để xóa tất cả các nhân viên được Checked trong ListView, sau khi xóa thành công thì phải cập nhật lại ListView.
– Để làm được điều trên thì ta sẽ kế thừa từ ArrayAdapter và override phương thức getView, cụ thể:
– Bạn xem Cấu trúc chương trình quản lý nhân viên:
– Tôi tạo thêm thư mục drawable và kéo thả 3 icon mà Tôi sử dụng vào (bạn cũng tạo thư mục tên y xì vậy). Nhớ là tên hình phải viết liền và chữ thường đầu tiên.
– Trong thư mục layout: Tôi tạo thêm my_item_layout.xml dùng để Custom lại ListView, dưới đây là cấu trúc XML của nó:
[code language=”xml”]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/LinearLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" >
<ImageView android:id="@+id/imgitem" android:layout_width="22dip" android:layout_height="22dip" android:paddingLeft="2dp" android:paddingRight="2dp" android:paddingTop="2dp" android:layout_marginTop="4dp" android:contentDescription="here" android:src="@drawable/ic_launcher" />
<TextView android:id="@+id/txtitem" android:layout_height="wrap_content" android:layout_width="0dip" android:layout_weight="2" android:layout_marginTop="4dp" android:paddingLeft="2dp" android:paddingRight="2dp" android:paddingTop="2dp" android:textSize="15sp" />
<CheckBox android:id="@+id/chkitem" android:layout_width="wrap_content" android:layout_height="wrap_content" />
</LinearLayout>
[/code]
– Ta sẽ dựa vào các id trong này để xử lý trong hàm getView của class mà ta kế thừa từ ArrayAdapter (các id trên là imgitem đại diện cho hình là Nữ hay Nam, txtitem dùng để hiển thị mã và tên nhân viên, chkitem dùng để xử lý Checked)
– Bạn xem activity_main.xml:
[code language=”xml”]
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/LinearLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity" >
<TextView android:id="@+id/textView2" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#008000" android:gravity="center" android:text="Quản lý nhân viên" android:textColor="#FFFFFF" android:textSize="20sp" />
<TableLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:stretchColumns="*" >
<TableRow android:id="@+id/tableRow1" android:layout_width="wrap_content" android:layout_height="wrap_content" >
<TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Mã NV:" />
<EditText android:id="@+id/editMa" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ems="10" >
<requestFocus />
</EditText>
</TableRow>
<TableRow android:id="@+id/tableRow2" android:layout_width="wrap_content" android:layout_height="wrap_content" >
<TextView android:id="@+id/textView4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Tên NV:" />
<EditText android:id="@+id/editTen" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ems="10" />
</TableRow>
<TableRow android:id="@+id/tableRow3" android:layout_width="wrap_content" android:layout_height="wrap_content" >
<TextView android:id="@+id/textView5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Giới tính:" />
<RadioGroup android:id="@+id/radioGroup1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" >
<RadioButton android:id="@+id/radNu" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="true" android:text="Nữ" />
<RadioButton android:id="@+id/radNam" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Nam" />
</RadioGroup>
</TableRow>
<TableRow android:id="@+id/tableRow4" android:layout_width="wrap_content" android:layout_height="wrap_content" >
<Button android:id="@+id/btnNhap" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_column="1" android:text="Nhập NV" />
</TableRow>
</TableLayout>
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" >
<TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="9" android:background="#008000" android:layout_marginTop="2dp" android:text="Danh sách nhân viên:" android:textColor="#FFFFFF" android:textSize="20sp" />
<ImageButton android:id="@+id/btndelete" android:layout_width="30dip" android:layout_height="30dip" android:src="@drawable/deleteicon" />
</LinearLayout>
<ListView android:id="@+id/lvnhanvien" android:layout_width="match_parent" android:layout_height="wrap_content" >
</ListView>
</LinearLayout>
[/code]
– Layout main này chính là giao diện chính của ứng dụng.
– Dưới đây là các class hỗ trợ xử lý nghiệp vụ:
– Class Employee dùng để lưu trữ thông tin nhân viên: Mã nhân viên, tên nhân viên, giới tính
– Class MyArrayAdapter kế thừa từ ArrayAdapter, mục đích của nó là giúp chúng ta Custom lại layout cho ListView.
– Cuối cùng MainActivity.
– Bây giờ ta vào chi tiết từng class:
1) class Employee:
[code language=”java”]
package tranduythanh.com;
public class Employee {
private String id;
private String name;
private boolean gender;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isGender() {
return gender;
}
public void setGender(boolean gender) {
this.gender = gender;
}
@Override
public String toString() {
return this.id+"-"+this.name;
}
}
[/code]
2) class MyArrayAdapter:
[code language=”java”]
package tranduythanh.com;
import java.util.ArrayList;
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class MyArrayAdapter extends
ArrayAdapter<Employee>
{
Activity context=null;
ArrayList<Employee>myArray=null;
int layoutId;
/**
* Constructor này dùng để khởi tạo các giá trị
* từ MainActivity truyền vào
* @param context : là Activity từ Main
* @param layoutId: Là layout custom do ta tạo (my_item_layout.xml)
* @param arr : Danh sách nhân viên truyền từ Main
*/
public MyArrayAdapter(Activity context,
int layoutId,
ArrayList<Employee>arr){
super(context, layoutId, arr);
this.context=context;
this.layoutId=layoutId;
this.myArray=arr;
}
/**
* hàm dùng để custom layout, ta phải override lại hàm này
* từ MainActivity truyền vào
* @param position : là vị trí của phần tử trong danh sách nhân viên
* @param convertView: convertView, dùng nó để xử lý Item
* @param parent : Danh sách nhân viên truyền từ Main
* @return View: trả về chính convertView
*/
public View getView(int position, View convertView,
ViewGroup parent) {
/**
* bạn chú ý là ở đây Tôi không làm:
* if(convertView==null)
* {
* LayoutInflater inflater=
* context.getLayoutInflater();
* convertView=inflater.inflate(layoutId, null);
* }
* Lý do là ta phải xử lý xóa phần tử Checked, nếu dùng If thì
* nó lại checked cho các phần tử khác sau khi xóa vì convertView
* lưu lại trạng thái trước đó
*/
LayoutInflater inflater=
context.getLayoutInflater();
convertView=inflater.inflate(layoutId, null);
//chỉ là test thôi, bạn có thể bỏ If đi
if(myArray.size()>0 && position>=0)
{
//dòng lệnh lấy TextView ra để hiển thị Mã và tên lên
final TextView txtdisplay=(TextView)
convertView.findViewById(R.id.txtitem);
//lấy ra nhân viên thứ position
final Employee emp=myArray.get(position);
//đưa thông tin lên TextView
//emp.toString() sẽ trả về Id và Name
txtdisplay.setText(emp.toString());
//lấy ImageView ra để thiết lập hình ảnh cho đúng
final ImageView imgitem=(ImageView)
convertView.findViewById(R.id.imgitem);
//nếu là Nữ thì lấy hình con gái
if(emp.isGender())
imgitem.setImageResource(R.drawable.girlicon);
else//nếu là Nam thì lấy hình con trai
imgitem.setImageResource(R.drawable.boyicon );
}
//Vì View là Object là dạng tham chiếu đối tượng, nên
//mọi sự thay đổi của các object bên trong convertView
//thì nó cũng biết sự thay đổi đó
return convertView;//trả về View này, tức là trả luôn
//về các thông số mới mà ta vừa thay đổi
}
}
[/code]
– Đây là class quan trọng nhất, mới nhất; dùng để custom layout.
3) class MainActivity:
[code language=”java”]
package tranduythanh.com;
import java.util.ArrayList;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.RadioGroup;
public class MainActivity extends Activity {
ArrayList<Employee>arrEmployee=new ArrayList<Employee>();
//Sử dụng MyArrayAdapter thay thì ArrayAdapter
MyArrayAdapter adapter=null;
ListView lvNhanvien=null;
Button btnNhap;
ImageButton btnRemoveAll;
EditText editMa,editTen;
RadioGroup genderGroup;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnNhap=(Button) findViewById(R.id.btnNhap);
btnRemoveAll=(ImageButton) findViewById(R.id.btndelete);
editMa=(EditText) findViewById(R.id.editMa);
editTen=(EditText) findViewById(R.id.editTen);
genderGroup=(RadioGroup) findViewById(R.id.radioGroup1);
lvNhanvien=(ListView) findViewById(R.id.lvnhanvien);
arrEmployee=new ArrayList<Employee>();
//Khởi tạo đối tượng adapter và gán Data source
adapter=new MyArrayAdapter(
this,
R.layout.my_item_layout,// lấy custom layout
arrEmployee/*thiết lập data source*/);
lvNhanvien.setAdapter(adapter);//gán Adapter vào Lisview
btnNhap.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
xulyNhap();
}
});
btnRemoveAll.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
xulyXoa();
}
});
}
//gọi hàm xử lý nhập thông tin nhân viên
public void xulyNhap()
{
String ma=editMa.getText()+"";
String ten=editTen.getText()+"";
boolean gioitinh=false;//Nam =false
if(genderGroup.getCheckedRadioButtonId()==R.id.radNu)
gioitinh=true;
//Tạo một employee
Employee emp=new Employee();
emp.setId(ma);
emp.setName(ten);
emp.setGender(gioitinh);
//Đưa vào danh sách
arrEmployee.add(emp);
//gọi hàm cập nhật giao diện
adapter.notifyDataSetChanged();
//Sau khi update thì xóa trắng dữ liệu và cho editma focus
editMa.setText("");
editTen.setText("");
editMa.requestFocus();
}
//hàm xử lý xóa
public void xulyXoa()
{
//ta nên đi ngược danh sách, kiểm tra phần tử nào checked
//thì xóa đúng vị trí đó ra khỏi arrEmployee
for(int i=lvNhanvien.getChildCount()-1;i>=0;i–)
{
//lấy ra dòng thứ i trong ListView
//Dòng thứ i sẽ có 3 phần tử: ImageView, TextView, Checkbox
View v=lvNhanvien.getChildAt(i);
//Ta chỉ lấy CheckBox ra kiểm tra
CheckBox chk=(CheckBox) v.findViewById(R.id.chkitem);
//Nếu nó Checked thì xóa ra khỏi arrEmployee
if(chk.isChecked())
{
//xóa phần tử thứ i ra khỏi danh sách
arrEmployee.remove(i);
}
}
//Sau khi xóa xong thì gọi update giao diện
adapter.notifyDataSetChanged();
}
}
[/code]
– Bây giờ bạn thực hiện chương trình và nhập một số nhân viên, rồi checked rồi nhấn xóa:
– Bạn hãy tìm hiểu thêm trên mạng về cách xử lý các phần tử khi custom, ở đây Tôi chưa xử lý xong: Ví dụ bạn chưa thể chọn được vào từng phần tử trong ListView (cho dù bạn có nghiến răng ngoáy mạnh ngón tay vào thì nó cũng không lung lay, Nếu bạn bỏ CheckBox đi thì lại được)… Nên bạn tìm hiểu thêm phần xử lý này (how to selected item in custom ListView).
– Bạn có thể tải toàn bộ coding mẫu ở đây:http://www.mediafire.com/?16tw6xf55nivr8k
– Bạn nên làm tốt bài này vì nó rất quan trọng và hay.
– Trong các bài tập tiếp theo bạn sẽ được thực hành về Spinner, GridView, kết hợp Spinner với ListView, kết hợp Spinner với GridView.
– Chúc các bạn thành công
Thầy ơi, em không thấy đối tượng nào gọi hàm getView hết, nhưng sao hàm này vẫn chạy, nếu bỏ nó đi thì khi nhấn nút thêm nhân viên chương trình lại báo lỗi?
Tôi có chung câu hỏi giống bạn, nhưng theo tôi dc biết thì phương thức override tự chạy ngầm. Cũng không rõ lắm, Hy vọng là thầy giúp đỡ
bạn nhấn getView rồi Ctrl+khoảng trắng sẽ tự @Overide cho mình nhé
Theo mình hiểu là hàm getView đấy trả lại khung nhìn my_item_layout.xml với những giá trị ta thêm vào. Khi ta setAdapter(adapter) cho ListView thì nó sẽ đc hiển thị.
//ta nên đi ngược danh sách, kiểm tra phần tử nào checked
//thì xóa đúng vị trí đó ra khỏi arrEmployee
for(int i=lvNhanvien.getChildCount()-1;i>=0;i–)
—–
sao lại “nên” đi ngược danh sách hả thầy ? nó có gì tốt hơn so với việc đi từ đầu danh sách theo cách thường làm
for(int i = 0; i<lvNhanvien.getChildCount(); i++)
Nếu đi xuôi từ i = 0 thì bạn luôn xóa giá trị đầu tiên. nên nếu đi ngược bản sẽ không phải check trường hợp này.
– Bạn chú ý hàm adapter.notifyDataSetChanged(); ở ngoài vòng for(::), tức là chạy hết vòng for() xong ListView mới được cập nhật lại.
(*) Vậy nếu bạn chạy vòng for(int i = 0; i Lúc này kích thước ArrayList = 3. Mà trong khi đó kích thước ListView vẫn còn 4 phần tử => bị Lỗi.
=> Nhưng nếu ngược lại for(int i=lvNhanvien.getChildCount()-1;i>=0;i–) hoàn toàn OK.
Không biết mình giải thích vậy đúng không nữa?
– Bạn chú ý hàm adapter.notifyDataSetChanged(); ở ngoài vòng for(::), tức là chạy hết vòng for() xong ListView mới được cập nhật lại.
(*) Vậy nếu bạn chạy vòng for(int i = 0; i Lúc này kích thước ArrayList = 3. Mà trong khi đó kích thước ListView vẫn còn 4 phần tử => bị Lỗi.
– Bạn chú ý hàm adapter.notifyDataSetChanged(); ở ngoài vòng for(::), tức là chạy hết vòng for() xong ListView mới được cập nhật lại.
(*) Vậy nếu bạn chạy:
for(int i = 0; i 4. Gặp 1, mảng sẽ xóa ptu đó đi.
Lúc này size() của ArrayList = 3. Mà trong khi đó kích thước ListView vẫn còn 4 phần tử => bị Lỗi.
Hình như Comment bị lỗi nên không hiển thị đúng cái mình muốn nói. Sorry!!
Chúng ta có thể đi từ đầu nhưng thêm 1 điều kiện là i–; sau mỗi câu lệnh remove.
for(int i = 0; i<lvNhanvien.getChildCount(); i++) {
…
// Remove item at position i
i–;
…
}
Tôi đã thử chay ngược lại và chẳng thấy 1 chút vấn đề gì, và cũng không phải “i-” như bạn Jackie nói. Tôi thấy chạy từ đầu mảng tới cuối không chút vấn đề gì. Mong tác giả lên tiếng lý do “nên chạy ngược”
Hi all
Trong trường hợp xóa theo nhiều vị trí ta cần chạy ngược để đám bảo rằng các vị trí đánh dấu xóa không bị xáo trộn, nếu xóa xuôi có thể bị xóa nhầm dòng không cần xóa do mỗi lần xóa 1 phần tử nó có thể dịch chuyển lên trên (tự co dãn lại danh sách) dẫn tới bị sai vị trí. Để kiểm tra trường hợp xáo trộn có bị xảy ra hay không thì ta thử checked xen kẽ các phần tử rồi chọn xóa.
Khi đặt thêm 2 giá trị này cho ô checkbox thì có thể click được vào các phần tử trong custome listview như bình thường.
android:focusable=”false”
android:focusableInTouchMode=”false”
Đã test và đã thành công.
ok thanks
Mình dùng ImgButton thay cho checkbox nhưng khi đặt 2 giá trị này vẫn không được. Bạn có thẻ giúp mình khắc phục nó? Cảm ơn bạn.
Thật là may mắn, 2 giá trị đó không thể thực hiện với ImageButton và mình đã tìm thấy nó ở đây http://mylifewithandroid.blogspot.com/2011/08/focus-problems-with-list-rows-and.html
Đó là thêm giá trị
android:descendantFocusability=”blocksDescendants”
Vào layout item (layout chứa các text, img, button chứ không phải chúng).
rất hữu ích
Hi, cho em hỏi với là: bắt cái sự kiện clicks vào checkbox đó như tnao ạ. Bởi đây nó là checkbox ở layout khác nên em ko triệu gọi hàm setonclick đc 🙁 . Bác chỉ giúp em với, tks bác
Thầy ơi, sao khi em thử với listview có nhiều item, khi e check vào 1 item rồi kéo để xem những item khác ở bên dưới làm item đã được check bị chạy ẩn đi thì khi e kéo lại thì nó tự động bị bỏ check vậy
thưa thầy! thầy có thể giải thích cho em hiểu cách sử dụng của LayoutInflater được không ạ, em thực sự chưa hiểu nó hoạt động thế nào.
cảm ơn thầy.
LayoutInflater là một component cơ bản của android nó dùng để chuyển mã từ file layout (.xml) lên view
Xin thầy hướng dẫn nếu khi nhấn Nhập NV khi đó dữ liệu sẽ lưu vào Database như thế nào..mong thầy và các bạn giúp..:)
Thầy có thể chỉ em cách khi nhập mã nhân viên kiểm tra không được phép nhập trùng mã.
Việc kiểm tra trùng mã rất đơn giản.
Cách 1: Đơn giản nhất
– Trước khi đưa NV vào danh sách thì lấy mã NV định đưa vào đem so sánh xem nó có tồn tại trong danh sách không (em dùng phương thức compareTo hoặc CompareIgnoreCase)
Cách 2: Implement interface comparer… (em search thêm trên mạng)
cái class custom layout kia loằng ngoằng quá, em đọc mãi mà không hỉu được 🙁
Làm sao để mình có thể quản lý được cái high của listview vậy thây. em muốn làm một cái list nó sẽ tự dài theo danh sách mình đưa vào. vì mình còn một số cái hiển thị phía dưới nữa nên khi ds ít sẽ hiển thị đủ trên màn hình.
thanks,
Làm sao để khi kéo listview lên xuống mà nó vẫn lưu lại vị trí mà mình đã check, thầy có thể hướng dẫn cho em các phương pháp để e làm tốt hơn được không ạ, em cảm ơn thầy
Cảm ơn thầy vì những bài tập của thầy rất bổ ích.
Em có vấn đề ngoài lề xíu, vấn đề về tự học.
Thầy cho em ý kiến với ạ. Em đọc code hiểu và làm lại (nhìn và làm theo), nhưng thực sự nếu không nhìn thì em làm lại không được. Thầy có thể cho em ý kiến của thầy về cách học của em được không.
Em cảm ơn thầy nhiều!
Thầy cho em hỏi, em dùng listview có nhiều item, khi em check vào 1 item rồi scroll xuống thì cũng sẽ có 1 item được check. khi em kiêm tra position thi thấy 2 checkbox này có position giống nhau ạ. Em cảm ơn thầy.
Có bạn nào xóa được không, mình xóa chỉ xóa dc phần tử đầu tiên theo code của thầy.Mong mọi người chỉ giúp
Thầy ơi cho em hỏi em làm đúng rồi không có lỗi mà sao chạy lên thì không add được Id và name vào listview ak. Chỉ hiện khoảng cách trống thôi ak
android:src=”@drawable/ic_launcher” <– không có hình ảnh ic_launcher trong drowable
Hi em. Hình đó em tự đưa bất kỳ hình nào vào trong drawable nhé
Thầy và các bạn có thể giải thích cho em hiểu tại sao tham số layoutId trong MyArrayAdapter lại có kể kết nối đến my_item_layout.xml được ạ. Em không thấy có dòng lệnh nào để ánh xạ hai cái này với nhau cả.
Hi em
Em để ý nó ánh xạ trong trong MainActivity, chỗ new customAdapter em nhé
Thầy Thanh