Download as pdf or txt
Download as pdf or txt
You are on page 1of 44

Bài giảng Lập Trình Di Động - Android

Chương 4. FRAGMENT VÀ GIAO DIỆN NÂNG CAO


Trong chương này, chúng ta sẽ tìm hiểu về khái niệm phân mảnh giao diện, cách
sử dụng chúng để tạo những giao diện phức tạp và một số đối tượng điều kiển hộp
thoại và điều kiển hiển thị dạng danh sách.

4.1. Android Fragment


4.1.1. Fragment là gì?

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.

ThS. Bùi Trung Úy 90


Bài giảng Lập Trình Di Động - Android

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.

4.1.2. Sử dụng Fragment

* Tạo mới Fragment


Để tạo một fragment mới, bạn tạo lớp và thừa kế từ lớp Fragment. Thiết lập giao
diện và implement các chức năng đóng gói của nó.

ThS. Bùi Trung Úy 91


Bài giảng Lập Trình Di Động - Android

Để 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ách 2: Dùng mã nguồn Java


Mỗi Activity có một FragmentManager để quản lý các fragment bên trong nó.
FragmentManager cung cấp các phương thức sử dụng để truy cập vào fragment hiện
tại đã được gắn vào Activity:
FragmentManager fragManager = getFragmentManager();
Sử dụng FragmentTransaction để thêm fragment vào Activity:
FragmentTransaction fragTransaction = fragManager.beginTransaction();
fragTransaction.add(R.id.details_fragment, new DetailsFragment());
fragTransaction.commit();
Ngoài ra, FragmentTransaction cũng có các thao tác khác như thêm, xóa hay thay
thế fragment, các thao tác này được gọi là một giao dịch. Mỗi giao dịch có thể gồm
nhiều thao tác thực hiện tại cùng thời điểm.
FragmentTransaction fragTransaction = fragManager.beginTransaction();
Fragment fragment = fragManager.findFragmentById(R.id.list_fragment);
fragTransaction.remove(fragment);
fragTransaction.commit();

ThS. Bùi Trung Úy 92


Bài giảng Lập Trình Di Động - Android

4.1.3. Vòng đời của Fragment

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

Giải thích sơ đồ:


Khi fragment được tạo và chạy, sẽ thực hiện theo thứ tự như sau:
onAttach(): Fragment được gắn vào activity. Mỗi Fragment muốn chạy được thì nó
phải thuộc vào một activity nào đó.
onCreate(): Khi một fragment mới được khởi tạo (hàm này luôn được gọi sau khi
fragment được attach vào Activity)
onCreateView(): Hàm này dùng để tạo giao diện cho fragment.
onActivityCreated(): Khi activity của fragment đã khởi tạo xong.
onStart(): Khi fragment hiển thị. Một fragment chỉ start sau khi Activity start và
thường là nó start ngay lập tức khi Activity start xong.
onResume(): Khi fragment hiển thị và có thể tương tác

ThS. Bùi Trung Úy 93


Bài giảng Lập Trình Di Động - Android

Các sự kiện khi bạn hoặc hệ thống destroy một fragment:


onPause(): Khi fragment không còn tương tác (có thể vẫn hiển thị). Điều này xảy
ra ngay cả khi fragment bị gỡ bỏ hoặc bị thay thế, hoặc là khi activity của fragment bị
tạm dừng.
onStop(): Khi fragment không còn hiển thị. Điều này xảy ra ngay sau khi fragment
bị gỡ bỏ hoặc thay thế, hoặc là khi activity của fragment bị tạm dừng.
onDestroyView(): Khi các view hay resource được tạo trong onCreateView bị
remove khỏi activity và destroy.
onDestroy(): Khi fragment kết thúc việc dọn dẹp.
onDetach(): Khi fragment bị tách khỏi Activity của nó.
Bạn có thể thấy vòng đời của fragment được đan xen với vòng đời của activity.
Tuy nhiên, vòng đời của Fragment có nhiều sự kiện trong vòng đời hơn so với
Activity. Để hiểu rõ hơn vòng đời của một Frament, chúng ta có thể ghi đè các hàm sự
kiện tương ứng và ghi ra log:
package com.example.helloandroid;

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();

ThS. Bùi Trung Úy 94


Bài giảng Lập Trình Di Động - Android

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

ThS. Bùi Trung Úy 95


Bài giảng Lập Trình Di Động - Android

Thực hành với Fragment:

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).

Các bước thực hiện:


1. Tạo mới một Activity có tên là FragmentActvity.java

ThS. Bùi Trung Úy 96


Bài giảng Lập Trình Di Động - Android

B2. Tiếp theo chúng ta tạo file top_fragment.xml:


Trên Android Studio chọn: „File/New/Layout resource file‟

Nhập thông tin như sau:

Thiết kế giao diện trên top_fragment.xml, có được xml như sau:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal"
>
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textColor="#FF0000"
android:layout_marginTop="10dp"
android:layout_gravity="center"
android:text="Image of 4 cities in Vietnam"
/>

ThS. Bùi Trung Úy 97


Bài giảng Lập Trình Di Động - Android

<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>

Tương tự tạo file bottom_fragment.xml, có XML như sau:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="25dp"
android:textColor="#0000FF"
android:layout_marginTop="20dp"
android:text=""/>
<ImageView

ThS. Bùi Trung Úy 98


Bài giảng Lập Trình Di Động - Android

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 {

ThS. Bùi Trung Úy 99


Bài giảng Lập Trình Di Động - Android

@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

Tương tự với BottomFragment.java, ta có nội dung file activity_fragment.xml:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical">
<fragment
android:id="@+id/fragTop"
android:name="com.example.helloandroid.TopFragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
<fragment
android:id="@+id/fragBottom"

ThS. Bùi Trung Úy 100


Bài giảng Lập Trình Di Động - Android

android:name="com.example.helloandroid.BottomFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

Sửa file AndroidManifest.xml cho FragmentActivity là activity chính như sau:


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.helloandroid">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".FragmentActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity" />
</application>
</manifest>

Chạy thử ứng dụng, ta có kết quả:

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;

ThS. Bùi Trung Úy 101


Bài giảng Lập Trình Di Động - Android

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;

ThS. Bùi Trung Úy 102


Bài giảng Lập Trình Di Động - Android

cityName = "City: Danang";


} else if(checkedRadioId== R.id.rdHanoi) {
imageCityId = R.drawable.hanoi;
cityName = "City: Ha Noi";
} else if(checkedRadioId== R.id.rdbSaigon) {
imageCityId = R.drawable.saigon;
cityName = "City: Saigon";
}
Toast.makeText(mActivity, "You choose:" + cityName, Toast.LENGTH_SHORT).show();
}

private void applyChange() {


mActivity.showCityImage(cityName, imageCityId);
}
}

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;

public class BottomFragment extends Fragment {


private TextView textView;
private ImageView cityView;

@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;
}

public void showCity(String cityName, int imageId) {


textView.setText(cityName);
cityView.setImageResource(imageId);
}
}

FragmentActivity.java
package com.example.helloandroid;

ThS. Bùi Trung Úy 103


Bài giảng Lập Trình Di Động - Android

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);
}

public void showCityImage(String cityName, int imageId) {


FragmentManager fragManager = this.getSupportFragmentManager();
BottomFragment bottomFragment
= (BottomFragment)fragManager.findFragmentById(R.id.fragBottom);
bottomFragment.showCity(cityName, imageId);
}
}

Chạy ứng dụng, thực hiện chọn city và nhấn nút „Apply‟ ta có kết quả:

ThS. Bùi Trung Úy 104


Bài giảng Lập Trình Di Động - Android

4.2. Trình đơn (menu) và hộp thoại (dialog)


4.2.1. Sử dụng trình đơn (menu)

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;
}
}

ThS. Bùi Trung Úy 105


Bài giảng Lập Trình Di Động - Android

Trình đơn chính (options menu)

Để 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);
}

Trình đơn ngữ cảnh (context menu)

Để 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:

ThS. Bùi Trung Úy 106


Bài giảng Lập Trình Di Động - Android

@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:

4.2.2. Sử dụng hộp thoại (dialog)

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) {

ThS. Bùi Trung Úy 107


Bài giảng Lập Trình Di Động - Android

Toast.makeText(getBaseContext(), "OK clicked!",


Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton("Cancel",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
Toast.makeText(getBaseContext(), "Cancel clicked!",
Toast.LENGTH_SHORT).show();
}
})
.setMultiChoiceItems(items, itemsChecked,
new DialogInterface.OnMultiChoiceClickListener() {
public void onClick(DialogInterface dialog, int
which, boolean isChecked) {
Toast.makeText(getBaseContext(), items[which] +
(isChecked ? " checked!":" unchecked!"), Toast.LENGTH_SHORT).show();
}
}).create();
}
return null;
}

Để 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

ThS. Bùi Trung Úy 108


Bài giảng Lập Trình Di Động - Android

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,…

4.3. Các điều khiển hiển thị dạng danh sách


4.3.1. Sử dụng ListView

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.

Các bước để tạo và sử dụng


* Bước 1: Khai báo ListView trong giao diện
Tương tự như các view khác, có thể dùng kéo-thả hoặc viết trong XML:
<ListView
android:id="@+id/listProduct"
android:layout_width="match_parent"
android:layout_height="match_parent" />

* Bước 2: Xác định cách hiển thỉ các phần tử (List-item)


Một ListView được tạo từ một danh sách các list-item. List-item là một dòng (row)
riêng lẻ trong listview nơi mà dữ liệu sẽ được hiển thị. Bất kỳ dữ liệu nào trong
listview chỉ được hiển thị thông qua list-item.
* Bước 3: Xây dựng nguồn cấp dữ liệu (Adapter)
Sau khi có một adapter cần thiết lập cho ListView, trong onCreate của Activity, ta
gán Adapter này cho listView như sau:

ThS. Bùi Trung Úy 109


Bài giảng Lập Trình Di Động - Android

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();

Xây dựng ListView đơn giản


Ta sẽ xem xét ListView trong trường hợp đơn giản nhất: hiển thị danh sách các
phần tử dạng chữ.

- Tạo một Activity mới là ListViewActivity.java.


- Tạo giao diện với ListView bên trong như sau:
activity_list_view.xml:
<?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">

<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

ThS. Bùi Trung Úy 110


Bài giảng Lập Trình Di Động - Android

= new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1 , <values>);


listView.setAdapter(arrayAdapter);

Code hoàn chỉnh của ListViewActivity là:


package com.example.helloandroid;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class ListViewActivity extends AppCompatActivity {


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_view);

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


String[] users = new String[]{"Tom (Admin)", "Jerry (Users)", "Donald (Guest)"};
ArrayAdapter<String> arrayAdapter
= new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1 , users);

listView.setAdapter(arrayAdapter);
}
}

Chạy ứng dụng ta có kết quả:

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

ThS. Bùi Trung Úy 111


Bài giảng Lập Trình Di Động - Android

Xây dựng tùy biến ListView nâng cao


Xét ví dụ, để hiển thị danh sách các quốc gia. Với các listItem và adapter như sau:

- Thiế kế tùy biến listItem:

ThS. Bùi Trung Úy 112


Bài giảng Lập Trình Di Động - Android

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.

ThS. Bùi Trung Úy 113


Bài giảng Lập Trình Di Động - Android

Country.java
package com.example.helloandroid;

public class Country {


private String countryName;
private String flagName;
private int population;

public Country(String countryName, String flagName, int population) {


this.countryName= countryName;
this.flagName= flagName;
this.population= population;
}
public int getPopulation() {
return population;
}
public String getCountryName() {
return countryName;
}
public String getFlagName() {
return flagName;
}
@Override
public String toString() {
return this.countryName+" (Population: "+ this.population+")";
}
}

CustomListAdapter.java
package com.example.helloandroid;

import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;

ThS. Bùi Trung Úy 114


Bài giảng Lập Trình Di Động - Android

import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;

public class CustomListAdapter extends BaseAdapter {


private List<Country> listData;
private LayoutInflater layoutInflater;
private Context context;

public CustomListAdapter(Context ctx, List<Country> listData) {


this.context = ctx;
this.listData = listData;
layoutInflater = LayoutInflater.from(ctx);
}
@Override
public int getCount() {
return listData.size();
}
@Override
public Object getItem(int position) {
return listData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.list_item_layout, null);
holder = new ViewHolder();
holder.flagView = (ImageView) convertView.findViewById(R.id.imageView_flag);
holder.countryNameView = (TextView)
convertView.findViewById(R.id.textView_countryName);
holder.populationView = (TextView) convertView.findViewById(R.id.textView_population);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Country country = this.listData.get(position);
holder.countryNameView.setText(country.getCountryName());
holder.populationView.setText("Population: " + country.getPopulation());
int imageId = this.getImageResIdByName(country.getFlagName());
holder.flagView.setImageResource(imageId);
return convertView;
}
// Tìm ID của Image ứng với tên của ảnh

ThS. Bùi Trung Úy 115


Bài giảng Lập Trình Di Động - Android

public int getImageResIdByName(String resName) {


String pkgName = context.getPackageName();
int resID = context.getResources().getIdentifier(resName, "drawable", pkgName);
return resID;
}

static class ViewHolder {


ImageView flagView;
TextView countryNameView;
TextView populationView;
}
}

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;

public class ListViewActivity extends AppCompatActivity {

@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;

Toast.makeText(getBaseContext(), "Selected item " + country.getCountryName(),


Toast.LENGTH_LONG).show();
}
});
}

ThS. Bùi Trung Úy 116


Bài giảng Lập Trình Di Động - Android

private List<Country> getListCountries() {


List<Country> list = new ArrayList<Country>();
Country vietnam = new Country("Vietnam", "vi", 98000000);
Country usa = new Country("United States", "us", 320000000);
Country russia = new Country("Russia", "ru", 142000000);
list.add(vietnam);
list.add(usa);
list.add(russia);

return list;
}
}

Chạy ứng dụng ta có kết quả:

4.3.2. Sử dụng SpinnerView

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"

ThS. Bùi Trung Úy 117


Bài giảng Lập Trình Di Động - Android

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;

public class MainActivity extends AppCompatActivity {


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Spinner
Spinner spinner1 = (Spinner) findViewById(R.id.spinner1);
final String[] users = new String[]{"Tom (Admin)", "Jerry (Users)", "Donald (Guest)"};
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, users);
spinner1.setAdapter(adapter);
spinner1.setOnItemSelectedListener(new Spinner.OnItemSelectedListener()
{
@Override
public void onItemSelected(AdapterView<?> arg0, View v, int position, long id)
{
Toast.makeText(getBaseContext(), "You have selected item : " + users[position],
Toast.LENGTH_SHORT).show();
}
@Override
public void onNothingSelected(AdapterView<?> arg0) { }
});
}
}

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:

ThS. Bùi Trung Úy 118


Bài giảng Lập Trình Di Động - Android

4.3.3. Sử dụng GridView

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

ThS. Bùi Trung Úy 119


Bài giảng Lập Trình Di Động - Android

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);

// Khi người dùng click vào một phần tử


gridView1.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View v, int position, long id) {
Toast.makeText(getBaseContext(), "You have selected item : " + users[position],
Toast.LENGTH_SHORT).show();
}
});
}

Chạy ứng dụng ta có kết quả:

ThS. Bùi Trung Úy 120


Bài giảng Lập Trình Di Động - Android

Chương 5. QUẢN LÝ DỮ LIỆU TRONG ANDROID


Trong chương này, chúng ta sẽ xem xét các cách thức lưu trữ dữ liệu trong
Android. Lưu trữ dữ liệu là một tính năng quan trọng đối với ứng dụng, giúp cho
người dùng có thể dùng lại được những dữ liệu trước đó mà không cần tải lại.

5.1. Dữ liệu trong Assets


Android Assets là thư mục chứa dữ liệu đầu vào. Chẳng hạn như âm thanh, hình
ảnh, cơ sở dữ liệu hoặc các tập tin khác,... Những tập tin này sẽ không được biên dịch
khi ứng dụng được đóng gói.

Để 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();
}

ThS. Bùi Trung Úy 121


Bài giảng Lập Trình Di Động - Android

// To load text file


InputStream input;
try {
input = assetManager.open("helloworld.txt");
int size = input.available();
byte[] buffer = new byte[size];
input.read(buffer);
input.close();

// byte buffer into a string


String text = new String(buffer)
txtView.setText(text);
}
catch (IOException e) {
e.printStackTrace();
}

// To load image
try {
InputStream ims = assetManager.open("android_logo_small.jpg");
// create drawable from stream
Drawable d = Drawable.createFromStream(ims, null);

// set the drawable to imageview


imageView.setImageDrawable(d);
}
catch(IOException ex) {
e.printStackTrace();
}
}

5.2. Dữ liệu trong SharedPreferences


SharedPreferences là một cơ chế cho phép bạn lưu trữ và đọc dữ liệu bằng các cặp
khóa-giá trị (key-value), dữ liệu nó có thể lưu là ở dạng nguyên thuỷ như: int, float,
string, boolean, long. Dữ liệu của Shared Preferences sẽ được lưu cục bộ ở trong
phạm vi ứng dụng, chính vì thế nếu xoá ứng dụng hoặc xoá dữ liệu của ứng dụng thì
dữ liệu này cũng sẽ bị xóa.
Đầu tiên, để lưu dữ liệu bạn cần khởi tạo một biến đối tượng kiểu Shared
Preferences như sau:
SharedPreferences sharedPrefs = getSharedPreferences(<SharedName>,
Context.MODE_PRIVATE);

Trong đó:
<SharedName>: là tên của Shared Preferences cần lưu.

ThS. Bùi Trung Úy 122


Bài giảng Lập Trình Di Động - Android

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ề.

Thực hành với shared Preferences:


Đây là một ví dụ về màn hình thiết lập thông số của một trò chơi trên Android,
trước khi chơi bạn lựa chọn các thông số như độ sáng, mức độ âm lượng, và độ khó.
Sau khi chơi xong bạn tắt trò chơi và có thể tiếp tục chơi vào ngày hôm sau.
SharedPreferences cho phép bạn lưu lại các các thông số đã thiết lập trước đó, để cho
phép khi chơi lại các thiết lập đó có thể sử dụng mà không cần phải thiết lập lại.

ThS. Bùi Trung Úy 123


Bài giảng Lập Trình Di Động - Android

Đọc dữ liệu settings từ Shared preferences:


private void loadGameSetting() {
SharedPreferences sharedPrefs = this.getSharedPreferences("gameSettings",
Context.MODE_PRIVATE);
if(sharedPreferences!= null) {
int brightness = sharedPrefs.getInt("brightness", 90);
int sound = sharedPrefs.getInt("sound",95);
int checkedRadioButtonId = sharedPrefs.getInt("checkedRadioButtonId",
R.id.radioButton_medium);

this.seekBarSound.setProgress(sound);
this.seekBarBrightness.setProgress(brightness);
this.radioGroupDiffLevel.check(checkedRadioButtonId);
}
}

Lưu dữ liệu setting vào Shared preferences.


public void saveGameSettings() {
SharedPreferences sharedPrefs= this.getSharedPreferences("gameSettings",
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putInt("brightness", this.seekBarBrightness.getProgress());
editor.putInt("sound", this.seekBarSound.getProgress());
// ID của RadioButton đang được chọn.
int checkedRadioButtonId = radioGroupDiffLevel.getCheckedRadioButtonId();
editor.putInt("checkedRadioButtonId", checkedRadioButtonId);
// Save.
editor.apply();
}

ThS. Bùi Trung Úy 124


Bài giảng Lập Trình Di Động - Android

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.

5.3. Lưu trữ dữ liệu trên bộ nhớ SD


Trong trường hợp cần lưu lại dữ liệu tương đối phức tạp hơn (khó có thể lưu lại
dạng key-value trong shared preference), ta có thể dùng hệ thống file. Trong Android,
để làm việc (nhập/xuất) với file, ta có thể sử dụng các lớp của gói java.io. Trong phần
này ta sẽ xem cách làm việc với file trong bộ nhớ trong lẫn bộ nhớ ngoài.

5.3.1. Sử dụng bộ nhớ trong

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:

ThS. Bùi Trung Úy 125


Bài giảng Lập Trình Di Động - Android

String simpleFileName = "note.txt";


FileInputStream in = this.openFileInput(simpleFileName);
Ví dụ đoạn code mẫu đọc/ghi dữ liệu:
Lưu dữ liệu vào bộ nhớ trong:
private void saveData() {
String data = this.editText.getText().toString();
try {
// Mở một luồng ghi file.
FileOutputStream out = this.openFileOutput(simpleFileName, MODE_PRIVATE);
// Ghi dữ liệu.
out.write(data.getBytes());
out.close();

Toast.makeText(this,"File saved!",Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Toast.makeText(this,"Error:"+ e.getMessage(),Toast.LENGTH_SHORT).show();
}
}

Đọc dữ liệu từ bộ nhớ trong:


private void readData() {
try {
// Mở một luồng đọc file.
FileInputStream in = this.openFileInput(simpleFileName);
BufferedReader br= new BufferedReader(new InputStreamReader(in));
StringBuilder sb= new StringBuilder();
String s= null;
while((s= br.readLine())!= null) {
sb.append(s).append("\n");
}
this.textView.setText(sb.toString());

} catch (Exception e) {
Toast.makeText(this,"Error:"+ e.getMessage(),Toast.LENGTH_SHORT).show();
}
}

5.2.2. Sử dụng bộ nhớ ngoài

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"/>

ThS. Bùi Trung Úy 126


Bài giảng Lập Trình Di Động - Android

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.

Phương thức Trả về

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.

Code mẫu đọc/ghi dữ liệu trên bộ nhớ ngoài:


public String fileName = “Note.txt”;
Ghi dữ liệu vào file trên bộ nhớ ngoài:
private void writeFile() {
// Thư mục gốc của SD Card.
File extStore = Environment.getExternalStorageDirectory();
String path = extStore.getAbsolutePath() + "/" + fileName;
String data = editText.getText().toString();

try {
File myFile = new File(path);
myFile.createNewFile();
FileOutputStream fOut = new FileOutputStream(myFile);
OutputStreamWriter myOutWriter = new OutputStreamWriter(fOut);
myOutWriter.append(data);
myOutWriter.close();

ThS. Bùi Trung Úy 127


Bài giảng Lập Trình Di Động - Android

fOut.close();
Toast.makeText(getApplicationContext(), fileName + " saved", Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
}
}

Đọc dữ liệu từ file trên bộ nhớ ngoài:


private void readFile() {
// Thư mục gốc của SD Card.
File extStore = Environment.getExternalStorageDirectory();
String path = extStore.getAbsolutePath() + "/" + fileName;
String s = "";
String fileContent = "";
try {
File myFile = new File(path);
FileInputStream fIn = new FileInputStream(myFile);
BufferedReader myReader = new BufferedReader(
new InputStreamReader(fIn));
while ((s = myReader.readLine()) != null) {
fileContent += s + "\n";
}
myReader.close();

this.textView.setText(fileContent);
} catch (IOException e) {
e.printStackTrace();
}
}

Liệt kê thư mục trên bộ nhớ ngoài:


private void listExternalStorages() {
StringBuilder sb = new StringBuilder();
sb.append("Data Directory: ").append("\n - ")
.append(Environment.getDataDirectory().toString()).append("\n");
sb.append("Download Cache Directory: ").append("\n - ")
.append(Environment.getDownloadCacheDirectory().toString()).append("\n");
sb.append("External Storage State: ").append("\n - ")
.append(Environment.getExternalStorageState().toString()).append("\n");
sb.append("External Storage Directory: ").append("\n - ")
.append(Environment.getExternalStorageDirectory().toString()).append("\n");
sb.append("Download Cache Directory: ").append("\n - ")
.append(Environment.getDownloadCacheDirectory().toString()).append("\n");
sb.append("Root Directory: ").append("\n - ")
.append(Environment.getRootDirectory().toString()).append("\n");
this.textView.setText(sb.toString());
}

5.4. Sử dụng cơ sở dữ liệu SQLLite


ThS. Bùi Trung Úy 128
Bài giảng Lập Trình Di Động - Android

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);
}

ThS. Bùi Trung Úy 129


Bài giảng Lập Trình Di Động - Android

// ...
}

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;

public class DatabaseHelper extends SQLiteOpenHelper {


private static final String TAG = "DatabaseHelper";
// Phiên bản
private static final int DATABASE_VERSION = 1;
// Tên cơ sở dữ liệu.
private static final String DATABASE_NAME = "AppDB.sqllite";
// Tên bảng
private static final String TABLE_NAME = "contacts";
private static final String FIELD_ID ="_id";
private static final String FIELD_NAME ="name";
private static final String FIELD_EMAIL = "email";

public DatabaseHelper(Context context) {


super(context, DATABASE_NAME, null, DATABASE_VERSION);
}

@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();
}
}

ThS. Bùi Trung Úy 130


Bài giảng Lập Trình Di Động - Android

@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();
}

ThS. Bùi Trung Úy 131


Bài giảng Lập Trình Di Động - Android

//---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";

ThS. Bùi Trung Úy 132


Bài giảng Lập Trình Di Động - Android

private static final String FIELD_NAME ="name";


private static final String FIELD_EMAIL = "email";

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);

ThS. Bùi Trung Úy 133

You might also like