Trong quá trình phát triển ứng dụng "Trí tuệ Bắc Kinh", việc xây dựng một khung giao diện người dùng (UI) hiệu quả là yếu tố then chốt. Bài viết này tập trung vào việc sử dụng Fragment để quản lý UI, tích hợp Sliding Menu cho thanh điều hướng bên và xử lý các tương tác người dùng.
I. Xây dựng Khung Giao diện với Fragment
1. Khái niệm Fragment: Fragment đại diện cho một phần giao diện người dùng có thể tái sử dụng và quản lý một cách độc lập trong một Activity. Triết lý "High Cohesion, Low Coupling" (Độ gắn kết cao, Độ ghép nối thấp) được áp dụng, cho phép phân mảnh và quản lý giao diện một cách linh hoạt. Fragment được Google khuyến khích sử dụng kể từ Android 4.0.
2. Vòng đời Fragment: Vòng đời Fragment song hành với vòng đời Activity, với các phương thức quan trọng như onCreateView() để khởi tạo giao diện.
3. Cấu trúc Fragment trong dự án: Để quản lý giao diện chính và thanh điều hướng bên, hai Fragment chính là LeftMenuFragment và ContentFragment được tạo ra. Một lớp trừu tượng BaseFragment được xây dựng để chứa các phương thức chung, giúp giảm thiểu mã lặp lại.
package com.itheima.zhbj74.fragment;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public abstract class BaseFragment extends Fragment {
public Activity mActivity; // Tham chiếu đến Activity chứa Fragment
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActivity = getActivity(); // Lấy Activity hiện tại
}
// Khởi tạo giao diện Fragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = initView(); // Gọi phương thức khởi tạo giao diện con
return view;
}
// Sau khi Activity của Fragment đã được tạo xong
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initData(); // Gọi phương thức khởi tạo dữ liệu con
}
// Phương thức trừu tượng để khởi tạo giao diện, phải được triển khai bởi lớp con
public abstract View initView();
// Phương thức trừu tượng để khởi tạo dữ liệu, phải được triển khai bởi lớp con
public abstract void initData();
}
MainActivity được cấu hình để sử dụng SlidingFragmentActivity, cho phép tích hợp Fragment vào cấu trúc Sliding Menu.
package com.itheima.zhbj74;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.view.Window;
import com.itheima.zhbj74.fragment.ContentFragment;
import com.itheima.zhbj74.fragment.LeftMenuFragment;
import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu;
import com.jeremyfeinstein.slidingmenu.lib.app.SlidingFragmentActivity;
public class MainActivity extends SlidingFragmentActivity {
private static final String TAG_LEFT_MENU = "TAG_LEFT_MENU";
private static final String TAG_CONTENT = "TAG_CONTENT";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE); // Loại bỏ tiêu đề
setContentView(R.layout.activity_main);
// Cấu hình SlidingMenu
setBehindContentView(R.layout.left_menu); // Thiết lập layout cho menu bên
SlidingMenu slidingMenu = getSlidingMenu();
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN); // Chế độ chạm toàn màn hình
slidingMenu.setBehindOffset(200); // Khoảng cách từ lề phải màn hình
initFragment(); // Khởi tạo các Fragment
}
/**
* Khởi tạo và thay thế các Fragment vào layout
*/
private void initFragment() {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
// Thay thế Fragment vào các FrameLayout tương ứng
transaction.replace(R.id.fl_left_menu, new LeftMenuFragment(), TAG_LEFT_MENU);
transaction.replace(R.id.fl_main, new ContentFragment(), TAG_CONTENT);
transaction.commit(); // Thực thi giao dịch
}
}
II. Phát triển Thanh Điều hướng Dưới (Bottom Tab Bar)
1. Thiết kế Layout: Sử dụng ViewPager để chứa nội dung chính và RadioGroup với các RadioButton để tạo thanh điều hướng dưới. Mỗi RadioButton đại diện cho một tab, với icon và văn bản tương ứng.
2. Tùy chỉnh RadioButton: Định nghĩa một style chung cho các RadioButton trong styles.xml để đảm bảo tính nhất quán. Sử dụng state list drawable (ví dụ: tab_home_selector) để thay đổi icon khi trạng thái được chọn.
3. Ngăn chặn ViewPager trượt ngang: Để chỉ cho phép chuyển đổi tab bằng cách nhấn vào RadioButton, một lớp NoScrollViewPager tùy chỉnh được tạo ra, ghi đè phương thức onTouchEvent() và onInterceptTouchEvent() để vô hiệu hóa chức năng trượt.
III. Tạo và Quản lý các Trang Nội dung
1. Cấu trúc trang: Mỗi trang nội dung (ví dụ: trang chủ, tin tức) sẽ bao gồm một thanh tiêu đề chung (với tiêu đề, nút menu) và một khu vực chứa nội dung chính sử dụng FrameLayout.
2. Lớp cơ sở BasePager: Một lớp cơ sở BasePager được tạo ra để quản lý giao diện chung cho cả 5 tab. Lớp này chứa các thành phần giao diện như TextView cho tiêu đề, ImageButton cho nút menu, và FrameLayout để chứa nội dung động.
package com.itheima.zhbj74.base;
import android.app.Activity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.TextView;
import com.itheima.zhbj74.R;
public class BasePager {
public Activity mActivity;
public TextView tvTitle; // Tiêu đề
public ImageButton btnMenu; // Nút menu
public FrameLayout flContent; // FrameLayout chứa nội dung động
public View mRootView; // View gốc của trang
public BasePager(Activity activity) {
mActivity = activity;
mRootView = initView(); // Khởi tạo layout cho trang
}
// Khởi tạo layout chung
public View initView() {
View view = View.inflate(mActivity, R.layout.base_pager, null);
tvTitle = view.findViewById(R.id.tv_title);
btnMenu = view.findViewById(R.id.btn_menu);
flContent = view.findViewById(R.id.fl_content);
return view;
}
// Khởi tạo dữ liệu, sẽ được ghi đè bởi các lớp con
public void initData() {
}
}
Các lớp con kế thừa từ BasePager sẽ định nghĩa nội dung cụ thể cho từng tab bằng cách thêm layout vào flContent.
IV. Tích hợp XUtils để Gọi API và Phân tích JSON
1. Yêu cầu: Để lấy dữ liệu tin tức, cần sử dụng một thư viện HTTP client. XUtils là một lựa chọn phổ biến.
2. Cấu hình: Thêm thư viện XUtils vào dự án và khai báo quyền truy cập Internet trong AndroidManifest.xml.
3. Gọi API: Sử dụng HttpUtils của XUtils để gửi yêu cầu GET đến API. Dữ liệu phản hồi (thường là JSON) sẽ được xử lý trong phương thức onSuccess().
4. Lớp cấu trúc dữ liệu (POJO): Tạo các lớp Java (ví dụ: NewsMenu, NewsMenuData, NewsTabData) tương ứng với cấu trúc JSON trả về từ API. Lưu ý rằng tên thuộc tính trong các lớp POJO phải khớp chính xác với tên trường trong JSON.
5. Phân tích JSON với Gson: Thêm thư viện Gson vào dự án. Sử dụng Gson().fromJson() để chuyển đổi chuỗi JSON thành các đối tượng Java đã định nghĩa.
V. Quản lý Bộ nhớ Đệm (Caching)
1. Mục đích: Giảm thiểu số lần gọi API, tiết kiệm lưu lượng truy cập và cải thiện trải nghiệm người dùng bằng cách hiển thị dữ liệu nhanh chóng từ bộ nhớ đệm.
2. Lớp tiện ích CacheUtils: Một lớp tiện ích được tạo ra để lưu trữ và truy xuất dữ liệu từ bộ nhớ đệm. Dữ liệu có thể được lưu trữ bằng SharedPreferences hoặc lưu vào tệp.
package com.itheima.zhbj74.utils;
import android.content.Context;
public class CacheUtils {
/**
* Lưu dữ liệu JSON vào bộ nhớ đệm với URL làm khóa.
* @param url URL của yêu cầu.
* @param json Chuỗi JSON trả về.
* @param ctx Context của Activity.
*/
public static void setCache(String url, String json, Context ctx) {
// Sử dụng SharedPreferences để lưu trữ đơn giản
PrefUtils.setString(ctx, url, json);
}
/**
* Lấy dữ liệu JSON từ bộ nhớ đệm.
* @param url URL của yêu cầu.
* @param ctx Context của Activity.
* @return Chuỗi JSON đã lưu hoặc null nếu không có.
*/
public static String getCache(String url, Context ctx) {
// Lấy dữ liệu từ SharedPreferences
return PrefUtils.getString(ctx, url, null);
}
}
3. Logic truy xuất dữ liệu: Trước khi gọi API, kiểm tra xem có dữ liệu trong bộ nhớ đệm hay không. Nếu có, sử dụng dữ liệu đệm. Nếu không, gọi API và lưu kết quả vào bộ nhớ đệm sau khi nhận được.
4. Chiến lược tối ưu hóa: Có thể áp dụng các chiến lược như làm mới dữ liệu định kỳ hoặc luôn gọi API song song với việc đọc bộ nhớ đệm để đảm bảo dữ liệu luôn cập nhật.
VI. Quản lý Thanh Điều hướng Bên (Sliding Menu)
1. Kích hoạt/Vô hiệu hóa: Thanh điều hướng bên chỉ nên được kích hoạt trên một số trang nhất định (ví dụ: trang Tin tức). Điều này được thực hiện bằng cách kiểm tra trang hiện tại và hiển thị/ẩn nút menu hoặc vô hiệu hóa chức năng trượt của SlidingMenu.
2. Thiết lập dữ liệu: Dữ liệu cho thanh điều hướng bên thường đến từ API (ví dụ: danh sách các chuyên mục tin tức). Dữ liệu này được phân tích cú pháp và hiển thị trong LeftMenuFragment, thường sử dụng ListView với một Adapter tùy chỉnh.
3. Xử lý sự kiện click: Khi người dùng chọn một mục trong thanh điều hướng bên, nội dung tương ứng sẽ được tải và hiển thị trong ContentFragment.
VII. Chuyển đổi Chi tiết Trang Nội dung
1. Lớp cơ sở BaseMenuDetailPager: Tương tự như BasePager, một lớp cơ sở BaseMenuDetailPager được tạo ra để quản lý các giao diện chi tiết khi người dùng chọn một mục trong thanh điều hướng bên.
2. Cập nhật nội dung FrameLayout: Khi người dùng chọn một mục từ thanh điều hướng bên, ContentFragment sẽ nhận yêu cầu và cập nhật nội dung của FrameLayout tương ứng bằng cách thay thế bằng trang chi tiết mới.
3. Đồng bộ tiêu đề và trạng thái: Đảm bảo tiêu đề hiển thị trên thanh công cụ luôn khớp với trang chi tiết đang được hiển thị. Xử lý các trường hợp ngoại lệ để đảm bảo trạng thái hiển thị của thanh điều hướng bên và trang chi tiết luôn đồng bộ.
4. Sửa lỗi: Xử lý các lỗi tiềm ẩn như chồng chéo giao diện, tiêu đề không cập nhật, hoặc sự không đồng bộ giữa thanh điều hướng bên và nội dung chính.