Professional Documents
Culture Documents
P2extract Pages From Bai Giang LT Di Dong Androidp2 8961
P2extract Pages From Bai Giang LT Di Dong Androidp2 8961
Trong phần trước chúng ta đã tìm hiểu qua Activty cơ bản là giao diện của một
“màn hình” ứng dụng, mỗi màn hình là một Activity. Tuy nhiên khi các máy tính bảng
ra đời với màn hình lớn hơn rất nhiều so với điện thoại truyền thống, cho phép thiết kế
với nhiều loại view khác nhau, và phát sinh nhu cầu dùng lại các view này trên các
màn hình khác nhau (điện thoại và máy tính bảng). Khái niệm fragment được sinh ra
nhằm phục vụ nhu cầu đó.
Có thể hiểu fragment như các “tiểu Activity”, chứa tập hợp các view khác bên
trong nó. Fragment luôn luôn được chứa trong một Activity hoặc một fragment khác,
mỗi Activity có thể chứa một hoặc nhiều fragment . Một ví dụ điển hình của việc sử
dụng fragment là trường hợp thiết kế “master-detail”, bao gồm 2 view: view tổng
quan chứa danh sách các đối tượng (danh sách tiêu đề các bài báo chẳng hạn), và view
chi tiết, hiển thị nội dung của đối tượng (bài báo) đang được chọn. Mỗi view như vậy
được đặt trong 1 fragment. Trên màn hình điện thoại, do kích thước hạn chế, 2
fragment này sẽ nằm trong 2 activity khác nhau, trong khi đối với màn hình máy tính
bảng, 2 fragment này nằm trên cùng một Activity. Thiết kế này giúp việc dùng lại
code được tối đa, logic của ứng dụng nằm trong 2 fragment, được dùng lại cho cả điện
thoại và máy tính bảng, còn Activity chỉ là khung chứa tối thiểu mã nguồn.
Khái niệm fragment mới được đưa vào từ phiên bản Android 3.0 HoneyComb, tuy
nhiên tính năng này cũng được Google bổ sung cho các API thấp hơn (từ Level 4)
thông qua thư viện hỗ trợ Android Support Library v4.
Yếu tố chính trong việc sử dụng Fragment là nó có thể giúp chúng ta xây dựng các
giao diện một cách chủ động và linh hoạt để có thể thích ứng với các kiểu màn hình có
kích thước khác nhau từ điện thoại cho đến máy tính bảng, nói chính xác hơn là tối ưu
hóa giao diện cho từng loại thiết bị, kích thước và độ phân giải khác nhau.
Mỗi Fragment là một module độc lập có ràng buộc chặt chẽ vào Activity mà nó
gắn vào. Fragment có thể được tái sử dụng trong nhiều Activity, và trong một activity
có thể được gắn vào nhiều Fragment.
Dưới đây là một số ưu điểm nổi bật mà Fragment mang lại:
Module hóa (modularity): Với các Activity phức tạp thì code sẽ được implement ở
các Fragment. Mỗi Fragment là một module độc lập. Điều này sẽ làm cho code dễ tổ
chức và bảo trì tốt hơn.
Tái sử dụng (reusability): Viết code implement các tương tác với người dùng hoặc
các phần UI vào fragment để có thể chia sẻ chúng với các Activity khác.
Hỗ trợ đa màn hình: Fragment cung cấp cách thức để trình bày giao diện người
dùng (UI) phù hợp và tối ưu cho các loại thiết bị Android có kích thước màn hình và
mật độ điểm ảnh khác nhau.
Để thiết lập giao diện cho fragment, bạn ghi đè phương thức onCreateView() và
thực hiện gắn UI cho fragment, phương thức này được hệ thống Android gọi khi đến
khi fragment vẽ layout của nó.
public class DetailsFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
// Đọc file xml tạo ra đối tượng View.
View view = inflater.inflate(R.layout.details_fragment, container, false);
return view;
}
}
Không giống như Activity, fragment không cần phải khai báo trong Manifest bởi
vì Fragment chỉ tồn tại khi nhúng nó vào một Activity và vòng đời sống cũng phụ
thuộc Activity này.
* Gắn fragment vào Activity
Cách 1: Thực hiện tham chiếu Fragment từ giao diện XML của Activity
Để gắn fragment vào giao diện của Activity, ta sử dụng thẻ <fragment> như sau:
<fragment
android:layout_width="fill_parent"
android:layout_height="170dp"
android:name="<packagename>.DetailsFragment"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
Cũng giống như activity, vòng đời của một fragment xảy ra khi trạng thái của nó
thay đổi. Một sự kiện trong vòng đời của Fragment xảy ra khi chúng ta có thể nhìn
thấy fragment đang hoạt động, hoặc là khi fragment không sử dụng được hoặc bị
destroy. Và cũng giống với Activity, bạn có thể override các hàm trong vòng đời
fragment như: onCreate(), onAttach(), onCreateView()… để thực hiện những tác vụ
mong muốn
Dưới đây là sơ đồ minh họa về vòng đời của Fragment
import android.content.Context;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.util.Log;
public class DetailsFragment extends Fragment {
@Override
public void onAttach(Context context) {
super.onAttach(context);
Log.d("Fragment", "onAttach");
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("Fragment", "onCreate");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
log.d("Fragment", "onCreateView");
return null;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d("Fragment", "onActivityCreated");
}
@Override
public void onStart() {
super.onStart();
Log.d("Fragment", "onStart");
}
@Override
public void onResume() {
super.onResume();
Log.d("Fragment", "onResume");
}
@Override
public void onPause() {
super.onPause();
Log.d("Fragment", "onPause");
}
@Override
public void onStop() {
super.onStop();
Log.d("Fragment", "onStop");
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d("Fragment", "onDestroyView");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("Fragment", "onDestroy");
}
@Override
public void onDetach() {
super.onDetach();
Log.d("Fragment", "onDetach");
}
}
Chạy ứng dụng trên thiết bị Android, thực hiện các thao tác bấm phím Home,
Back, mở ứng dụng khác… bạn sẽ thấy hiển thị log về các sự kiện xảy ra trong vòng
đời của các Fragment:
11-03 23:24:29.398: D/Fragment (11354): onAttach
11-03 23:24:29.398: D/Fragment (11354): onCreate
11-03 23:24:29.398: D/Fragment (11354): onCreateView
11-03 23:24:29.398: D/Fragment (11354): onActivityCreated
11-03 23:24:29.398: D/Fragment (11354): onStart
11-03 23:24:29.398: D/Fragment (11354): onResume
11-03 23:24:33.835: D/Fragment (11354): onPause
11-03 23:24:33.835: D/Fragment (11354): onStop
11-03 23:24:33.835: D/Fragment (11354): onDestroyView
11-03 23:24:33.835: D/Fragment (11354): onDestroy
11-03 23:24:33.835: D/Fragment (11354): onDetach
Tạo dự án với bố cục giao diện như sau (với các hình ảnh như ví dụ trước).
<RadioGroup android:id="@+id/rdGpCities"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal" >
<RadioButton
android:id="@+id/rdDalat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Dalat" />
<RadioButton
android:id="@+id/rdDanang"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Danang" />
<RadioButton
android:id="@+id/rdHanoi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hanoi" />
<RadioButton
android:id="@+id/rdbSaigon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Saigon" />
</RadioGroup>
<Button
android:id="@+id/btApply"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Apply" />
</LinearLayout>
android:id="@+id/cityView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="fitCenter" />
</LinearLayout>
Mỗi Fragment sẽ tương ứng với một lớp trong Java. Lớp này mở rộng từ lớp
Fragment. Tạo 2 lớp TopFragment.java và BottomFragment.java và sửa code của nó
như sau.
TopFragment.java
package com.example.helloandroid;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class TopFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
// Đọc file xml tạo ra đối tượng View.
View view = inflater.inflate(R.layout.top_fragment, container, false);
return view;
}
}
BottomFragment.java
package com.example.helloandroid;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class BottomFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
// Đọc file xml tạo ra đối tượng View.
View view= inflater.inflate(R.layout.bottom_fragment, container, false);
return view;
}
}
Và bây giờ, bạn cần bố trí các fragment trên giao diện chính của FragmentActivity.
Mở file activity_fragment.xml, kéo thẻ <fragment> vào giao diện và chọn
TopFragment.java
android:name="com.example.helloandroid.BottomFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Tiếp theo, bạn cần phải viết code cho 3 lớp FragmentActivity.java,
TopFragment.java, BottomFragment.java để xử lý các sự kiện.
TopFragment.java
package com.example.helloandroid;
import android.app.Activity;
import android.content.Context;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.RadioGroup;
import android.widget.Toast;
public class TopFragment extends Fragment {
private int imageCityId;
private String cityName = "";
private FragmentActivity mActivity;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
// Đọc file xml tạo ra đối tượng View.
View view = inflater.inflate(R.layout.top_fragment, container, false);
// Đăng ký sự kiện khi RadioGroup Cities có thay đổi.
RadioGroup rdGpCities = (RadioGroup) view.findViewById(R.id.rdGpCities);
rdGpCities.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
doOnCitiesChanged(group, checkedId);
}
});
Button applyButton = (Button) view.findViewById(R.id.btApply);
applyButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
applyChange();
}
});
return view;
}
// Phương thức này được gọi sau khi Fragment được gắn vào Activity.
@Override
public void onAttach(Context context) {
super.onAttach(context);
Log.d("Fragment", "onAttach");
if (context instanceof FragmentActivity) {
this.mActivity = (FragmentActivity) context;
}
}
// Khi radio group "Cities" có thay đổi.
private void doOnCitiesChanged(RadioGroup group, int checkedId) {
int checkedRadioId = group.getCheckedRadioButtonId();
if(checkedRadioId== R.id.rdDalat) {
imageCityId = R.drawable.dalat;
cityName = "City: Dalat";
} else if(checkedRadioId== R.id.rdDanang ) {
imageCityId = R.drawable.danang;
BottomFragment.java
package com.example.helloandroid;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
// Đọc file xml tạo ra đối tượng View.
View view = inflater.inflate(R.layout.bottom_fragment, container, false);
textView = (TextView) view.findViewById(R.id.textView);
cityView = (ImageView) view.findViewById(R.id.cityView);
return view;
}
FragmentActivity.java
package com.example.helloandroid;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class FragmentActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
}
Chạy ứng dụng, thực hiện chọn city và nhấn nút „Apply‟ ta có kết quả:
Trình đơn dùng để hiển thị các hành động thường ít dùng hơn và không hiển thị
trực tiếp lên màn hình. Trong Android có 2 loại trình đơn:
- Trình đơn chính (options menu) – hiển thị các hành động liên quan đến toàn bộ
Activity hiện tại. Trong Android, để kích hoạt trình đơn này, ta bấm phím Menu của
thiết bị (phím cứng hoặc phím ảo trên màn hình)
- Trình đơn ngữ cảnh (context menu) – hiển thị các hành động liên quan đến một
view cụ thể trên màn hình, trình đơn này được kích hoạt bằng cách bấm và giữ (long
tap) trên view để kích hoạt trình đơn ngữ cảnh.
Về mặt lập trình, 2 loại menu này sử dụng cùng một lớp (android.view.Menu) và
chứa các đối tượng giống nhau (android.view.MenuItem), vì vậy ta tạo sẵn 2 phương
thức để dùng chung cho 2 loại menu này. Để tạo menu với các item bên trong và xử lý
sự kiện bấm vào từng item như dưới đây:
Hàm createMenu() dùng để thêm 7 items vào menu có sẵn:
private void createMenu(Menu menu)
{
menu.setQwertyMode(true);
MenuItem mnu1 = menu.add(0, 0, 0, "Item 1");
mnu1.setAlphabeticShortcut('a');
MenuItem mnu2 = menu.add(0, 1, 1, "Item 2");
mnu2.setAlphabeticShortcut('b');
MenuItem mnu3 = menu.add(0, 2, 2, "Item 3");
mnu3.setAlphabeticShortcut('c');
mnu3.setIcon(R.mipmap.ic_launcher);
menu.add(0, 3, 3, "Item 4");
menu.add(0, 4, 4, "Item 5");
menu.add(0, 5, 5, "Item 6");
menu.add(0, 6, 6, "Item 7");
}
Hàm menuChoice để xử lý sự kiện tương ứng với menu item được lựa chọn
(truyền vào dạng tham số). Với mục đích minh họa, khi một menu item được chọn, ta
chỉ đơn gian hiển thị tên của item đó dưới dạng Toast.
private boolean menuChoice(MenuItem item) {
switch (item.getItemId()) {
case 0:
Toast.makeText(this, "You choose on Item 1", Toast.LENGTH_LONG).show();
default:
Toast.makeText(this, "You click Item id = " + item.getItemId(), Toast.LENGTH_LONG).show();
return true;
}
}
Để hiển thị trình đơn chính, ta nạp chồng hàm onCreateOptionMenu() của Activity
và thêm các menu item vào đối tượng menu (trong tham số của hàm):
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
// Tạo menu
createMenu(menu);
return true;
}
Để hiển thị menu này lúc chạy ứng dụng, ta bấm phím MENU của thiết bị hoặc nút
More (3 chấm đứng) trên Actionbar.
Để xử lý sự kiện khi chọn một item trong menu, ta cần nạp chồng hàm:
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
return menuChoice(item);
}
Để hiển thị trình đơn ngữ cảnh, ta nạp chồng hàm onCreateContextMenu() và
thêm các menu item vào đối tượng menu (trong tham số của hàm):
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo)
{
super.onCreateContextMenu(menu, view, menuInfo);
createMenu(menu);
}
Để xử lý sự kiện chọn một item trong menu, ta cần nạp chồng hàm sau:
@Override
public boolean onContextItemSelected (MenuItem item)
{
return menuChoice(item);
}
Để gắn menu ngữ cảnh này cho một đối tượng (chẳng hạn, nút bấm button), ta
thêm code sau vào hàm onCreate của activity:
Button btn = (Button) findViewById(R.id.button1);
btn.setOnCreateContextMenuListener(this);
Để hiển thị menu này lúc chạy ứng dụng, ta nhấn và giữ lên trên đối tượng, trình
đơn hiện ra như hình bên dưới:
Trong rất nhiều trường hợp chúng ta cần hiển thị thông báo, hỏi xác nhận của
người dùng, hiển thị trạng thái chờ… ở dạng hộp thoại nhanh hiện lên trên Activity
hiện tại, mà không cần mở ra Activity mới, khi đó ta cần nạp chồng hàm
onCreateDialog() của Activity.
Ví dụ dưới đây mô tả cách tạo một hộp thoại cơ bản như vậy:
CharSequence[] items = { "Google", "Apple", "Microsoft" };
boolean[] itemsChecked = new boolean [items.length];
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case 0:
return new AlertDialog.Builder(this)
.setIcon(R.mipmap.ic_launcher)
.setTitle("This is a dialog")
.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
Để hiển thị dialog ta viết sự kiện cho một nút bấm và gọi hàm showDialog(id). Kết
quả chạy ứng dụng và bấm vào nút, hộp thoại như hình bên dưới sẽ hiện ra:
Trong ví dụ trên ta đã nạp chồng phương thức onCreateDialog(int id) để hiển thị
hộp thoại khi có yêu cầu. Trong Activity có thể hiển thị nhiều hộp thoại khác nhau tùy
vào ngữ cảnh xử lý như hộp thoại thông báo, hộp thoại xác nhận, hộp thoại tiến
trình…, tham số id trong phương thức onCreateDialog là để phân biệt hộp thoại nào
cần được hiện lên.
Để kích hoạt yêu cầu mở hộp thoại, ta gọi phương thức showDialog(id). Trong
hàm onCreateDialog, tùy thuộc vào id truyền vào mà ta hiển thị hộp thoại tương ứng,
trong ví dụ trên là hộp thoại có id = 0. Trong trường hợp này ta sử dụng loại hộp thoại
đơn giản nhất là AlertDialog, để tạo ra một hộp thoại loại này, ta tạo ra một object của
class AlertDialog.Builder và gọi phương thức create() của nó. Đoạn code trên thiết lập
2 nút bấm cho hộp thoại cho trường hợp đồng ý (nút “OK” – setPositiveButton) và
hủy bỏ (nút “Cancel” – setNegativeButton), cũng như thiết lập các ô checkbox
(setMultiChoiceItems)
Các hàm xử lý sự kiện trong Dialog trong ví dụ chỉ đơn giản là hiển thị lên màn
hình dòng thông báo dạng text trong một khoảng thời gian ngắn. Dạng thông báo này
trong Android gọi là Toast - là đoạn chữ hiển thị ở giữa, phía dưới màn hình trong
khoảng vài giây. Toast thường được dùng để hiển thị các loại thông báo ngắn, ít quan
trọng như thông báo SMS, lưu dữ liệu thành công,…
ListView là một view group, hiển thị các thành phần (elements) theo một danh
sách, có thể cuộn được theo chiều thẳng đứng. ListView là một view quan trọng, nó
được sử dụng rộng rãi trong các ứng dụng Android.
Một ví dụ đơn giản của ListView là danh bạ liên lạc, nơi bạn có một danh sách các
số điện thoại liên lạc hiển thị trong một ListView.
listView.setAdapter(productAdapter);
Khi đã gán Adapter vào ListView, thì ListView sẽ dùng Adapter này xác định cần
hiển thị bao nhiêu phần tử, mỗi phần tử có view như thế nào (list-item) do Adapter tạo
và gắn vào ListView, mỗi khi dữ liệu do Adapter quản lý thay đổi, cần thông báo cho
ListView biết mà cập nhật bằng cách gọi:
listAdapter.notifyDataSetChanged();
<ListView
android:id="@+id/listView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
- Tiếp theo ta sẽ sử dụng Adapter và List-item định nghĩa sẵn trong Android
+ android.R.layout.simple_list_item_1: Là một hằng số list-item định nghĩa sẵn,
đây là loại list-item chỉ hiển thị duy nhất một TextView.
+ Sử dụng ArrayAdapter để cung cấp nguồn dữ liệu cho listview. Trong onCreate
của Activity, ta gán adapter này như sau:
ArrayAdapter<String> arrayAdapter
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
listView.setAdapter(arrayAdapter);
}
}
Ngoài ra, Android còn cung cấp một số các list-item đơn giản khác để sử dụng với
ArrayAdapter là:
+ android.R.layout.simple_list_item_checked
+ android.R.layout.simple_list_item_multiple_choice
list_item_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView_flag"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="5dp" />
<TextView
android:id="@+id/textView_countryName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Country Name"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/imageView_flag"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_above="@+id/textView_population"
android:layout_margin="5dp" />
<TextView
android:id="@+id/textView_population"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Population: ..."
android:layout_alignBottom="@+id/imageView_flag"
android:layout_alignLeft="@+id/textView_countryName"
android:layout_alignStart="@+id/textView_countryName"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_margin="5dp" />
</RelativeLayout>
- Xây dựng lớp CustomListAdapter tùy biến thừa kế từ lớp BaseAdapter, nó làm
nhiệm vụ hiển thị dữ liệu lên các List-item. Ta cũng tạo lớp Country để chứa dữ liệu
của mỗi country trên mỗi hàng.
Country.java
package com.example.helloandroid;
CustomListAdapter.java
package com.example.helloandroid;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
ListViewActivity.java
package com.example.helloandroid;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_view);
final ListView listView = (ListView)findViewById(R.id.listView);
List<Country> countries = getListCountries();
listView.setAdapter(new CustomListAdapter(this, countries));
// Khi người dùng click vào các ListItem
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> a, View v, int position, long id) {
Object obj = listView.getItemAtPosition(position);
Country country = (Country) obj;
return list;
}
}
ListView rất tiện dụng cho việc hiển thị danh sách các phần tử đồng dạng. Tuy
nhiên ListView chiếm tương đối nhiều diện tích trên màn hình. Trong thực tế có nhiều
trường hợp ta chỉ cần hiển thị phần tử đang chọn của danh sách, khi bấm vào phần tử
này, sẽ hiện ra danh sách đầy đủ các phần tử còn lại để ta lựa chọn. Để làm được việc
này, ta dùng SpinnerView. Để dễ hình dung, ta có thể hiểu SpinnerView chính là
ComboBox trong lập trình web và Windows Form.
Trong ví dụ dưới đây, ta hiển thị danh sách các phần tử đơn giản dạng chữ như ví
dụ trước, tuy nhiên dùng SpinerView thay cho ListView.
Trước tiên ta thêm khai báo một SpinnerView trong file layout của Activity:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Spinner
android:id="@+id/spinner1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawSelectorOnTop="true" />
</LinearLayout>
Sau đó, trong hàm onCreate của Activity, ta cần thêm mã nguồn để truy xuất đến
SpinnerView này, đặt adapter cho nó và thêm hàm xử lý sự kiện khi ta chọn một phần
tử của spinner:
package com.example.helloandroid;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.Toast;
Khi một phần tử của SpinnerView được chọn, ta chỉ đơn thuần in lên màn hình
thông báo dạng Toast. Chạy ứng dụng vừa tạo lên thiết bị hoặc emulator, ta sẽ quan
sát thấy spinner view sẽ có dạng như sau:
GridView giống với ListView nhưng có chức năng hỗ trợ hiển thị dữ liệu theo
dạng lưới. GridView cũng dựa vào Adapter để gắn kết các dữ liệu bên dưới, điểm
khác nhau là GridView có thiết lập số cột. Dữ liệu đưa vào dưới dạng mảng hay danh
sách, nhưng dựa vào số cột ta thiết lập mà nó tự động ngắt hàng dựa vào thuộc tính
numColums trong layout.
Ví dụ, tạo một gridView đơn giản hiển thị các phần tử dạng chử:
- Thêm GridView vào layout của Activity:
<?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">
<GridView
android:id="@+id/gridView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:numColumns="3" >
</GridView>
</RelativeLayout>
- Trong hàm onCreate của Activity gắn apdapter cho GridView để hiển thị dữ liệu:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final String[] users = new String[]{"Tom (Admin)", "Jerry (Users)", "Donald (Guest)",
"Bui (Admin)", "Trung (Guest)", "Uy (Users)"};
// GridView
GridView gridView1 = (GridView) findViewById(R.id.gridView1);
ArrayAdapter<String> adapter1 = new ArrayAdapter<String>
(this, android.R.layout.simple_list_item_1, users);
gridView1.setAdapter(adapter1);
Để truy xuất được những tập tin trong thư mục assets, ta phải sử dụng bộ quản lý
asset do Android cung cấp. Lớp này tên là AssetManager.
AssetManager assetManager = Context.getAssets();
AssetManager là lớp cung cấp quyền truy cập vào các tập tin không biên dịch của
ứng dụng, cho phép bạn mở và đọc file nguyên gốc dưới dạng một luồng byte. Chẳng
hạn, để truy xuất font chữ lưu trong thư mục Asset ta sử dụng như sau:
Typeface arial = Typeface.createFromAsset(assetManager, “font/arial.ttf”);
textView.setTypeface(arial);
Ví dụ, sau đây sẽ cho ta cái nhìn tổng quát về cách thức sử dụng AssetManager để
truy xuất tập tin trên thư mục assets.
public void loadAssetsData() {
AssetManager assetManager = getAssets();
// To get names of all files inside the "Files" folder
try {
String[] files = assetManager.list("Files");
for(int i=0; i<files.length; i++){
txtFileName.append("\n File :"+i+" Name => "+files[i]);}
} catch (IOException e1) {
e1.printStackTrace();
}
// To load image
try {
InputStream ims = assetManager.open("android_logo_small.jpg");
// create drawable from stream
Drawable d = Drawable.createFromStream(ims, null);
Trong đó:
<SharedName>: là tên của Shared Preferences cần lưu.
Context.MODE_PRIVATE: là chế độ bảo mật dữ liêu trong Android, khi bạn để như
vậy có nghĩa là bạn chỉ cho ứng dụng hiện tại truy cập vào file Shared Preferences này
và không một ứng dụng nào có quyền truy cập vào được.
Tiếp theo, tạo đối tượng editor từ biến sharedPrefs đã tạo ở trên, mục đích là để có
thể mở file và đưa dữ liệu vào:
SharedPreferences.Editor editor = sharedPrefs.edit();
Để đưa dữ liệu vào chúng ta sử dụng như sau:
editor.put<X>(String key, value)
Trong đó:
<X>: là kiểu dữ liệu bạn đưa vào, với Shared Preferences bạn có thể lưu ở nhưng
kiểu dữ liệu như : float, string, int, boolean, long.
key: là tên đặt cho biến bạn sẽ lưu xuống.
value: giá trị cần lưu.
Ví dụ:
SharedPreferences sharedPrefs = getSharedPreferences(“appSettings”,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString("appName", "HelloAndroid");
editor.putBoolean("FirstLaunch", true);
editor.putInt("HighScore",100);
editor.putFloat("Mark", 9.5f);
editor.putLong("Sound",90);
editor.commit(); // hoặc: editor.apply();
Sau khi đã put dữ liệu xong thì bạn gọi hàm commit() hoặc là apply() để xác nhận
những thay đổi. Sự khác nhau giữa commit() và apply() là:
commit(): hoạt động theo cơ chế đồng bộ, nếu như bạn khởi tạo 2 editor để chỉnh
sửa dữ liệu thì editor nào thực hiện trước sẽ làm trước và cái nào đến sau sẽ làm sau.
Và bạn sẽ được thông báo là true hay false nếu như thành công hoặc thất bại.
apply() : hoạt động theo cơ chế không đồng bộ, và dù có thành công hay không thì
sẽ không nhận được kết quả trả về.
this.seekBarSound.setProgress(sound);
this.seekBarBrightness.setProgress(brightness);
this.radioGroupDiffLevel.check(checkedRadioButtonId);
}
}
Việc lưu trữ dữ liệu Shared preferences là trong suốt với người dùng. Trên thực tế
chúng được lưu trong một file xml nằm trên bộ nhớ trong, trong vùng nhớ chỉ có thể
truy cập được bởi ứng dụng (/data/data/ {package-name} /shared_prefs/
gameSettings.xml), và với máy ảo Android ta có thể xem được cụ thể các giá trị này.
Android Internal Storage là nơi lưu trữ các dữ liệu cá nhân của từng ứng dụng, mà
các dữ liệu này được lưu trữ và sử dụng cho riêng ứng dụng đó. Các ứng dụng khác
không thể truy cập vào được. Thông thường khi ứng dụng bị gỡ bỏ khỏi thiết bị
Android, các file dữ liệu liên quan cũng bị xóa bỏ theo. Các files của ứng dụng sẽ
được lưu trong thư mục riêng tư /data/data/{package_name}.
Một đặc điểm khi bạn làm việc với các file dữ liệu ở bộ nhớ trong là bạn chỉ cần
dùng tên file đơn giản mà không phải thêm đường dẫn.
- Mở file ghi dữ liệu.
String simpleFileName ="note.txt";
FileOutputStream out = openFileOutput(simpleFileName, MODE_PRIVATE);
MODE_PRIVATE: Đây là chế độ mặc định, file ghi ra chỉ được sử dụng bởi ứng
dụng tạo ra nó, hoặc chia sẻ với cùng User ID.
MODE_APPEND : Chế độ nối thêm dữ liệu vào file nếu nó đã tồn tại.
- Mở file đọc dữ liệu:
Toast.makeText(this,"File saved!",Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Toast.makeText(this,"Error:"+ e.getMessage(),Toast.LENGTH_SHORT).show();
}
}
} catch (Exception e) {
Toast.makeText(this,"Error:"+ e.getMessage(),Toast.LENGTH_SHORT).show();
}
}
Android External Storage là nơi lưu trữ dữ liệu ngoài của Android, các file dữ liệu
mà bạn lưu trữ tại đây có thể được truy xuất bởi ứng dụng khác.
Để đọc ghi dữ liệu trên bộ lưu trữ ngoài bạn phải khai báo quyền sử dụng trong file
AndroidManifest.xml như sau:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
Sử dụng các phương tĩnh của lớp Environment bạn có thể lấy được các thông tin
về thư mục của các bộ lưu trữ ngoài.
Environment.getDataDirectory() /data
Environment.getDownloadCacheDirectory() /cache
Environment.getExternalStorageState() mounted
Environment.getDownloadCacheDirectory() /cache
Environment.getRootDirectory() /system
Ví dụ: Thiết kế ứng dụng theo mẫu và thực hiện đọc ghi dữ liệu trên bộ nhớ ngoài.
try {
File myFile = new File(path);
myFile.createNewFile();
FileOutputStream fOut = new FileOutputStream(myFile);
OutputStreamWriter myOutWriter = new OutputStreamWriter(fOut);
myOutWriter.append(data);
myOutWriter.close();
fOut.close();
Toast.makeText(getApplicationContext(), fileName + " saved", Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
}
}
this.textView.setText(fileContent);
} catch (IOException e) {
e.printStackTrace();
}
}
Trong phần trước ta đã tìm hiểu cách lưu dữ liệu vào file và vào shared
preferences. Tuy nhiên, với loại dữ liệu quan hệ thì sử dụng cơ sở dữ liệu quan hệ sẽ
thuận tiện hơn rất nhiều. Ví dụ ta cần lưu trữ kết quả kiểm tra của các sinh viên trong
trường học, dùng cơ sở dữ liệu sẽ cho phép chúng ta truy vấn kết quả học tập của sinh
viên theo các tiêu chí khác nhau, việc thêm, bớt, thay đổi thông tin thông qua các câu
truy vấn SQL cũng dễ dàng hơn nhiều so với việc thao tác trên file. Android sử dụng
hệ cơ sở dữ liệu SQLite.
1. SQLite là gì?
SQLite là một cơ sở dữ liệu quan hệ, mã nguồn mở và được tích hợp sẵn trên
Android. SQLite thường được sử dụng trong các ứng dụng ở local, như các ứng dụng
Danh bạ, Tin nhắn, Ghi chú, Quản lý thông tin cá nhân, các tùy chọn thiết lập trong
phần mềm,…
Trong Android, cơ sở dữ liệu (CSDL) do một ứng dụng tạo ra sẽ chỉ được truy
xuất bởi ứng dụng đó, và file CSDL sẽ nằm trong bộ nhớ trong dành riêng cho ứng
dụng (/data/data/{package-name}/databases/).
2. Các bước làm việc với SQLLite
Nói chung, bạn cần tạo một lớp tiện ích để làm việc với cơ sở dữ liệu SQLite, lớp
này nên mở rộng từ lớp SQLiteOpenHelper. Có 2 phương thức quan trọng mà bạn cần
phải ghi đè (override) nó là onCreate() và onUpgrade().
- onCreate() - là nơi mà chúng ta cần phải viết để tạo bảng. Nó được gọi khi cơ sở
dữ liệu được tạo ra.
- onUpgrade() - phương thức này được gọi khi cơ sở dữ liệu được nâng cấp như
thay đổi cấu trúc bảng, thêm ràng buộc cho cơ sở dữ liệu, v..v.
Cấu trúc của lớp tiện ích này (có tên DatabaseHelper) như sau:
public class DatabaseHelper extends SQLiteOpenHelper {
// ....
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// Script to create table.
String script = "CREATE TABLE " + TABLE_NAME + …;
// Execute script.
db.execSQL(script);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Drop table
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
// Recreate
onCreate(db);
}
// ...
}
Một thói quen tốt thường được các lập trình viên kinh nghiệm sử dụng là tập trung
tất cả mã lệnh truy cập đến CSDL vào một lớp riêng để thao tác trên CSDL và việc
truy xuất này sẽ trở nên trong suốt với môi trường ngoài.
Trong ví dụ sau ta sẽ tạo một CSDL tên là AppDB.sqllite, chứa một bảng duy nhất
là contacts, bảng này chứa các trường _id, name và email. Ta tạo lớp tiện ích
DatabaseHelper như sau:
package com.example.helloandroid;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
@Override
public void onCreate(SQLiteDatabase db) {
try {
// Script tạo bảng.
String sqlStr = "CREATE TABLE " + TABLE_NAME + "("
+ FIELD_ID + " INTEGER PRIMARY KEY," + FIELD_NAME + " TEXT not null,"
+ FIELD_EMAIL + " TEXT not null" + ")";
// Chạy lệnh tạo bảng.
db.execSQL(sqlStr);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
try {
// Hủy (drop) bảng cũ nếu nó đã tồn tại.
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
// Tạo lại lại bảng mới
onCreate(db);
} catch (SQLException e) {
e.printStackTrace();
}
}
//---insert a contact into the database---
public long insertContact(String name, String email) throws SQLException
{
SQLiteDatabase db = this.getWritableDatabase();
ContentValues args = new ContentValues();
args.put(FIELD_NAME, name);
args.put(FIELD_EMAIL, email);
// Chèn một dòng dữ liệu vào bảng.
long id = db.insert(TABLE_NAME, null, args);
// Đóng kết nối database.
db.close();
return id;
}
//---deletes a particular contact---
public boolean deleteContact(long id) throws SQLException
{
SQLiteDatabase db = this.getWritableDatabase();
boolean res = db.delete(TABLE_NAME, FIELD_ID + "=" + id, null) > 0;
db.close();
return res;
}
//---retrieves a particular contact---
public void getContact(long id) throws SQLException
{
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(true, TABLE_NAME, new String[] {FIELD_ID,
FIELD_NAME, FIELD_EMAIL}, FIELD_ID + "=" + id, null, null, null, null, null);
/*Cursor cursor = db.query(TABLE_NAME, new String[] { FIELD_ID,
FIELD_NAME, FIELD_EMAIL }, FIELD_ID + "=?",
new String[] { String.valueOf(id) }, null, null, null, null); */
if (cursor != null) {
cursor.moveToFirst();
// Hiển thị contact
displayContact(cursor);
}
// Đóng kết nối database.
db.close();
}
//---updates a contact---
public boolean updateContact(long rowId, String name, String email)
{
SQLiteDatabase db = this.getWritableDatabase();
ContentValues args = new ContentValues();
args.put(FIELD_NAME, name);
args.put(FIELD_EMAIL, email);
boolean res = db.update(TABLE_NAME, args, FIELD_ID + "=" + rowId, null) > 0;
// Đóng kết nối database.
db.close();
return res;
}
//---retrieves all the contacts---
public int getAllContacts() throws SQLException
{
SQLiteDatabase db = this.getWritableDatabase();
/*Cursor cursor = db.query(TABLE_NAME, new String[] {FIELD_ID, FIELD_NAME,
FIELD_EMAIL}, null, null, null, null, null);*/
// Select All Query
String selectQuery = "SELECT * FROM " + TABLE_NAME;
Cursor cursor = db.rawQuery(selectQuery, null);
// contacts count
int count = cursor.getCount();
// Duyệt trên con trỏ
if (cursor.moveToFirst()) {
do {
// Hiển thị contact
displayContact(cursor);
} while (cursor.moveToNext());
}
cursor.close();
return count;
}
public void displayContact(Cursor c)
{
Log.d(TAG, "Contact details: " +
"id: " + c.getString(0) + "\n" +
"Name: " + c.getString(1) + "\n" +
"Email: " + c.getString(2));
}
}
Trước tiên ta khai báo các hằng số: tên CSDL, tên bảng, tên các trường để dễ dàng
truy xuất và thay đổi trong quá trình phát triển. Ngoài ra ta cũng khai báo phiên bản
của CSDL trong ứng dụng và viết sẵn câu truy vấn dùng để tạo CSDL.
private static final String DATABASE_NAME = "AppDB.sqllite";
private static final String TABLE_NAME = "contacts";
private static final String FIELD_ID ="_id";
Trong lớp DatabaseHelper trợ giúp cho việc tạo CSDL và nâng cấp cấu trúc khi có
sự thay đổi trong các phiên bản tiếp theo. Lớp này kế thừa từ lớp SQLiteOpenHelper.
Hàm dựng của lớp này sẽ gọi hàm dựng của lớp cha với tên và phiên bản CSDL của
ứng dụng:
super(context, DATABASE_NAME, null, DATABASE_VERSION);
Ngoài ra trong lớp này ta ghi đè 2 hàm:
- Hàm onCreate(): được gọi để khởi tạo CSDL trong lần đầu tiên chạy ứng dụng,
trong hàm này ta tiến hành thực thi câu lệnh tạo CSDL ở trên.
- Hàm onUpgrade(): được gọi khi ta nâng cấp ứng dụng và thay đổi giá trị của
phiên bản CSDL. Trong ví dụ ở trên, khi có thay đổi phiên bản này, ta xóa CSDL cũ
và tạo lại cái mới.
Ngoài ra ta cũng viết thêm các hàm để tạo mới bản ghi, cập nhật bản ghi, lấy tất cả
bản ghi, lấy bản ghi theo id,…
Sau khi có lớp DatabaseHelper này, việc truy xuất CSDL trở nên tương đối đơn
giản, đoạn mã dưới đây minh họa các thao tác thêm, bớt, truy vấn CSDL:
//--- thêm một bản ghi ---
long id = db.insertContact("Le Hoang", "lehoang@gmail.com");
long id = db.insertContact("Bui Uy", "uybt@dau.edu.com");
//-- lấy danh sách tất cả bản ghi ---
db.getAllContacts();
//--- lấy một bản ghi theo id ---
db.getContact(2);
//--- cập nhật bản ghi ---
db.updateContact(1, "Tran Hao", "tranhao@gmail.com");
//--- xóa bản ghi ---
db.deleteContact(1);