Trong phần này chúng ta tiếp tục tìm hiểu một số lớp View
cơ bản là ProgressBar
và ListView.
ProgressBar
ProgressBar
hiển thị một thanh ngang hoặc một hình tròn biểu diễn tiến trình hoạt động của một hành động nào đó.
Ví dụ 1:
Chúng ta sẽ thiết kế một ProgressBar
và một TextView
hiển thị tiến trình hoạt động theo %.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ProgressBar android:id="@+id/pbId" android:layout_width="fill_parent" android:layout_height="wrap_content" style="?android:attr/progressBarStyleHorizontal" android:layout_margin="10dp" /> <TextView android:id="@+id/tvId" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_margin="10dp" /> </LinearLayout>
Trong file main.xml
chúng ta thiết kế một thẻ ProgressBar
và một TextView.
Thuộc tính style
quy định kiểu “vẽ” của ProgressBar
lên màn hình, ở đây progressBarStyleHorizontal
nghĩa là vẽ kiểu thanh ngang. Mặc định thì ProgressBar
sẽ vẽ theo kiểu hình tròn.
package com.phocode; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.widget.ProgressBar; import android.widget.TextView; import android.util.Log; public class MainActivity extends Activity { private ProgressBar pb; private TextView tv; private int prg = 0; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); pb = (ProgressBar) findViewById(R.id.pbId); tv = (TextView) findViewById(R.id.tvId); new Thread(myThread).start(); } private Runnable myThread = new Runnable() { @Override public void run() { while (prg < 100) { try { hnd.sendMessage(hnd.obtainMessage()); Thread.sleep(100); } catch(InterruptedException e) { Log.e("ERROR", "Thread was Interrupted"); } } runOnUiThread(new Runnable() { public void run() { tv.setText("Finished"); } }); } Handler hnd = new Handler() { @Override public void handleMessage(Message msg) { prg++; pb.setProgress(prg); String perc = String.valueOf(prg).toString(); tv.setText(perc+"% completed"); } }; }; }
Chúng ta sẽ điều khiển tiến trình hoạt động của ProgressBar
trong một luồng khác.
new Thread(myThread).start();
Ở đây chúng ta sử dụng một luồng khác để thực hiện công việc tính toán, còn luồng chính sẽ cập nhật ProgressBar,
lý do sử dụng 2 luồng khác nhau là để chúng có thể chạy song song với nhau.
@Override public void run() { while (prg < 100) { try { hnd.sendMessage(hnd.obtainMessage()); Thread.sleep(100); } catch(InterruptedException e) { Log.e("ERROR", "Thread was Interrupted"); } } runOnUiThread(new Runnable() { public void run() { tv.setText("Finished"); } }); }
Công việc tính toán ở đây chỉ đơn giản là tăng giá trị của một biến từ 0 đến 100, mỗi lần tăng thì nghỉ 100 mili giây.
Cơ chế của hệ điều hành Android có hơi khác các hệ điều hành khác là những biến nào được khai báo trong một Activity
chỉ có thể truy xuất được trong Activity
đó, biến myThread
tuy thuộc lớp Activity
chính nhưng thực chất nó chạy một luồng của riêng nó, không đụng chạm gì tới Activity
chính cả, do đó chúng ta không thể thao tác trực tiếp với các biến prg
hay biến TextView
tv
bên trong phương thức run()
được. Để có thể thao tác với các biến của một đối tượng Activity
từ một luồng khác, chúng ta phải gọi chúng bên trong phương thức runOnUiThread()
hoặc bên trong một đối tượng Handler.
runOnUiThread(new Runnable() { public void run() { tv.setText("Finished"); } });
Biến thuộc luồng nào thì chỉ có thể truy xuất được từ luồng đó, ở đây chúng ta chỉnh sửa biến tv
nên chúng ta truy xuất nó bên trong phương thức runOnUiThread().
Handler hnd = new Handler() { @Override public void handleMessage(Message msg) { prg++; pb.setProgress(prg); String perc = String.valueOf(prg).toString(); tv.setText(perc+"% completed"); } };
Như đã nói, ngoài cách dùng phương thức runOnUiThread(),
chúng ta cũng có thể truy xuất các biến đó thông qua một đối tượng Handler.
Ở đây chúng ta cập nhật biến prg
và thiết lập giá trị của biến đó cho ProgressBar.
Ví dụ 2:
Trong ví dụ này, chúng ta sẽ hiển thị ProgressBar
ở dạng hình tròn.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ProgressBar android:id="@+id/pbId" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/tvId" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Please wait..." /> </LinearLayout>
Ở đây chúng ta không thiết lập biến style
cho ProgressBar
nữa và như thế ProgressBar
sẽ hiển thị hình tròn thay vì thanh ngang. Ngoài ra TextView
cũng không hiển thị số % tiền trình hoạt động nữa nên chúng ta thiết lập “cứng” thuộc tính text
luôn.
package com.phocode; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.widget.ProgressBar; import android.widget.TextView; import android.view.View; import android.util.Log; public class MainActivity extends Activity { private ProgressBar pb; private TextView tv; private int prg = 0; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); pb = (ProgressBar) findViewById(R.id.pbId); tv = (TextView) findViewById(R.id.tvId); new Thread(myThread).start(); } private Runnable myThread = new Runnable() { @Override public void run() { while (prg < 100) { try { hnd.sendMessage(hnd.obtainMessage()); Thread.sleep(100); } catch(InterruptedException e) { Log.e("ERROR", "Thread was Interrupted"); } } runOnUiThread(new Runnable() { public void run() { tv.setText("Finished"); pb.setVisibility(View.GONE); } }); } Handler hnd = new Handler() { @Override public void handleMessage(Message msg) { prg++; pb.setProgress(prg); } }; }; }
Đoạn code trong file MainActivity.java
cũng tương tự như ví dụ trước.
runOnUiThread(new Runnable() { public void run() { tv.setText("Finished"); pb.setVisibility(View.GONE); } });
Chỉ khác là ở đây chúng ta không cập nhật text cho TextView
theo % mà chỉ canh khi nào tiến trình hoàn tất thì mới cập nhật thôi. Ngoài ra sau khi hoàn tất công việc, chúng ta cho ẩn ProgressBar
đi bằng phương thưc setVisibility()
.
ListView
ListView
hiển thị dữ liệu dưới dạng một danh sách các item, chúng ta có thể dùng tay cuộn danh sách lên xuống nếu danh sách quá dài. ListView
sử dụng dữ liệu được cung cấp bởi lớp Adapter.
Ví dụ 1:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ListView android:id="@+id/lvId" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>
Trong file main.xml
chúng ta khai báo một thẻ ListView.
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="10dp" android:textSize="20sp"> </TextView>
File row.xml
sẽ định nghĩa cách các hàng trong ListView
được hiển thị như thé nào, ví dụ như các hàng có độ lớn bao nhiêu, kích cỡ chữ bao nhiêu. Ở đây sp
là đơn vị dùng cho kích thước font trong Android.
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">MainActivity</string> <string-array name="languages"> <item>Python</item> <item>Java</item> <item>Ruby</item> <item>C++</item> </string-array> </resources>
Trong file strings.xml
chúng ta định nghĩa các item sẽ được dùng cho ListView
trong biến string-array.
package com.phocode; import android.app.Activity; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.ListView; public class MainActivity extends Activity { private ListView lv; private ArrayAdapter<String> la; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); setupUI(); } public void setupUI() { lv = (ListView) findViewById(R.id.lvId); String[] languages = getResources().getStringArray(R.array.languages); lv.setAdapter(new ArrayAdapter<String>(this, R.layout.row, languages)); } }
Trong file MainActivity.java
chúng ta kết nối tất cả những thứ trên lại với nhau.
String[] languages = getResources().getStringArray(R.array.languages);
Dòng code trên sẽ lấy dữ liệu của các item trong file strings.xml
đưa vào mảng languages.
lv.setAdapter(new ArrayAdapter<String>(this, R.layout.row, languages));
Lớp ArrayAdapter
có nhiệm vụ kết nối file row.xml,
dữ liệu trong mảng languages
vào đối tượng ListView.
Ví dụ 2:
Trong ví dụ này chúng ta sử dụng lớp ListActivity
thay cho lớp Activity
thường. Lớp ListActivity
là lớp Activity
nhưng có sẵn một đối tượng ListView.
Bởi vì ListView
rất thường được dùng để hiển thị riêng trong một màn hình Activity,
do đó Android cho ra đời lớp ListActivity
để đơn giản hóa việc thiết kế cho các coder. Cũng chính vì vậy ở đây chúng ta cũng sẽ không dùng đến file main.xml
để thiết kế.
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="10dp" android:textSize="20sp"> </TextView>
File row.xml
định nghĩa cách ListView
hiển thị, giống như ví dụ trước.
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">MainActivity</string> </resources>
File strings.xml
chỉ lưu tiêu đề của ứng dụng chứ không còn lưu danh sách các item nữa.
package com.phocode; import android.app.ListActivity; import android.os.Bundle; import android.view.View; import android.widget.ArrayAdapter; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ListView; import android.widget.TextView; public class MainActivity extends ListActivity implements OnItemClickListener, OnItemSelectedListener { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ArrayAdapter<String> la = new ArrayAdapter<String>(this, R.layout.row); la.add("Python"); la.add("Java"); la.add("Ruby"); la.add("C++"); setListAdapter(la); ListView lv = getListView(); lv.setOnItemClickListener(this); lv.setOnItemSelectedListener(this); } public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String language = ((TextView) view).getText().toString(); setTitle(language); } public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { String language = ((TextView) view).getText().toString(); setTitle(language); } public void onNothingSelected(AdapterView<?> parent) { } }
Thay vào đó chúng ta sẽ thiết kế các item cho ListView
trong Java. Ngoài ra chúng ta bắt sự kiện click vào item trên ListView
nữa.
public class MainActivity extends ListActivity implements OnItemClickListener, OnItemSelectedListener
Lớp MainActivity
giờ đây sẽ kế thừa lớp ListActivity
và implement 2 giao diện là OnItemClickListener
và OnItemSelectedListener,
tổng cộng có 3 phương thức trừu tượng cần được code.
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... }
Ở đây phương thức onCreate()
sẽ không có dòng setContentView()
vì mặc định lớp ListActivity
đã có sẵn View
của riêng nó là ListView
rồi, ListView
này có kích thước phủ toàn bộ màn hình.
ArrayAdapter<String> la = new ArrayAdapter<String>(this, R.layout.row); la.add("Python"); la.add("Java"); la.add("Ruby"); la.add("C++");
Chúng ta tạo đối tượng ArrayAdapter
như bình thường để truyền dữ liệu vào ListView.
setListAdapter(la);
Phương thức setListAdapter()
sẽ gắn dữ liệu của đối tượng Adapter
với đối tượng ListView.
ListView lv = getListView(); lv.setOnItemClickListener(this); lv.setOnItemSelectedListener(this);
Chúng ta thiết lập các listener cho ListView.
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String language = ((TextView) view).getText().toString(); setTitle(language); }
Giao diện OnItemClickListener
có phương thức trừu tượng là onItemClick(),
ở đây chúng ta làm công việc là thiết lập đoạn text trên thanh tiêu đề là text của item được click.
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { String language = ((TextView) view).getText().toString(); setTitle(language); } public void onNothingSelected(AdapterView<?> parent) { }
Giao diện OnItemSelectedListener
có 2 phương thức trừu tượng, tuy nhiên chúng ta chỉ code phương thức onItemSelected()
thôi, ở đây chúng ta cũng chỉ đơn giản là thiết lập lại tiêu đề của ứng dụng bằng tiêu đề của item được chọn trong ListView.