Series Java Swing Bài 29: FocusListener - Quản lý con trỏ chuột và Hiệu ứng Placeholder
Trong thế giới giao diện người dùng (UI), khái niệm "Focus" (Tiêu điểm) dùng để chỉ việc một Component đang được chọn và sẵn sàng nhận các thao tác từ bàn phím. Ví dụ: khi bạn click chuột vào một ô nhập liệu, bạn sẽ thấy một con trỏ nhấp nháy xuất hiện trong ô đó — ô đó đang giữ "Focus".
Swing cung cấp FocusListener để giúp bạn theo dõi xem khi nào một Component nhận được Focus và khi nào nó đánh mất Focus.
1. FocusListener hoạt động như thế nào?
FocusListener là một Interface chứa đúng 2 phương thức để mô tả 2 trạng thái của vòng đời Focus. Bạn cũng có thể dùng FocusAdapter nếu chỉ muốn ghi đè (override) 1 trong 2 hàm.
-
focusGained(FocusEvent e): Kích hoạt khoảnh khắc con trỏ chuột "bước vào" ô nhập liệu (hoặc khi người dùng dùng phím Tab di chuyển tới). -
focusLost(FocusEvent e): Kích hoạt khoảnh khắc con trỏ chuột "rời khỏi" ô nhập liệu (người dùng click sang một ô khác).
Ứng dụng 1: Đổi màu nền (Highlight) ô đang nhập
Đây là một trải nghiệm UX (User Experience) rất tốt. Khi người dùng click vào ô nào, ô đó sáng lên để họ biết mình đang gõ ở đâu.
Java
JTextField txtName = new JTextField();
txtName.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
// Đổi màu nền sang vàng nhạt khi đang nhập
txtName.setBackground(new Color(255, 255, 204));
}
@Override
public void focusLost(FocusEvent e) {
// Trả lại màu trắng khi click sang ô khác
txtName.setBackground(Color.WHITE);
}
});
2. Chủ động điều hướng con trỏ (requestFocus)
Trong thực tế, khi người dùng bấm nút "Đăng ký", bạn kiểm tra thấy họ nhập sai Email. Bạn sẽ hiện thông báo lỗi, nhưng sau khi tắt thông báo, con trỏ chuột nên tự động nhảy về đúng ô Email bị lỗi để họ gõ lại, thay vì bắt họ phải cầm chuột lên click lại vào ô đó.
Phương thức quyền lực đó là: requestFocus()
Java
if (txtEmail.getText().isEmpty()) {
JOptionPane.showMessageDialog(this, "Bạn chưa nhập Email!");
// Điều hướng con trỏ nhấp nháy quay lại ô Email
txtEmail.requestFocus();
}
3. Tuyệt chiêu: Tạo hiệu ứng Placeholder (Chữ mờ gợi ý)
Nếu bạn thiết kế web (HTML), bạn chỉ cần gõ thuộc tính placeholder="Nhập tên của bạn" là xong. Tuy nhiên, Java Swing không hỗ trợ Placeholder mặc định. Chúng ta bắt buộc phải tự "chế" ra tính năng này bằng cách kết hợp FocusListener và logic thay đổi màu chữ.
Thuật toán mô phỏng Placeholder:
-
Lúc khởi tạo: Set nội dung ô Text là chữ Placeholder (Ví dụ: "Tìm kiếm..."), đổi màu chữ thành xám mờ.
-
Khi Focus Gained (Click vào): Nếu nội dung đang là chữ Placeholder, ta lập tức xóa trắng ô Text và đổi màu chữ về Đen chuẩn bị cho người dùng gõ thật.
-
Khi Focus Lost (Click ra ngoài): Kiểm tra xem người dùng có gõ chữ nào không. Nếu ô Text trống rỗng, ta lại set nội dung về "Tìm kiếm..." và đổi màu xám.
4. Source Code Thực Chiến: Form Liên Hệ Chuẩn UX Hiện Đại
Đoạn mã sau tích hợp cả 3 kỹ thuật: Hàm tạo Placeholder dùng chung (có thể tái sử dụng cho hàng chục ô text), hiệu ứng đổi màu viền/nền khi đang nhập, và điều hướng con trỏ khi báo lỗi.
Java
import javax.swing.*;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
public class FocusListenerDemo extends JFrame {
private JTextField txtName;
private JTextField txtPhone;
public FocusListenerDemo() {
setTitle("Làm chủ Focus - Hiệu ứng Placeholder");
setSize(400, 300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 10, 10, 10);
gbc.fill = GridBagConstraints.HORIZONTAL;
// ==========================================
// 1. TẠO CÁC Ô NHẬP LIỆU
// ==========================================
gbc.gridx = 0; gbc.gridy = 0;
add(new JLabel("Họ và tên:"), gbc);
gbc.gridx = 1; gbc.gridy = 0; gbc.weightx = 1.0;
txtName = new JTextField(20);
setupPlaceholder(txtName, "Vd: Nguyen Van A"); // Gọi hàm gắn Placeholder
add(txtName, gbc);
gbc.gridx = 0; gbc.gridy = 1; gbc.weightx = 0.0;
add(new JLabel("Số điện thoại:"), gbc);
gbc.gridx = 1; gbc.gridy = 1; gbc.weightx = 1.0;
txtPhone = new JTextField(20);
setupPlaceholder(txtPhone, "Vd: 0987654321"); // Gọi hàm gắn Placeholder
add(txtPhone, gbc);
// ==========================================
// 2. NÚT XÁC NHẬN & TỰ ĐỘNG FOCUS KHI LỖI
// ==========================================
gbc.gridx = 1; gbc.gridy = 2;
JButton btnSubmit = new JButton("Gửi thông tin");
add(btnSubmit, gbc);
btnSubmit.addActionListener(e -> {
// Lấy dữ liệu (Phải cẩn thận loại trừ trường hợp dữ liệu chính là chuỗi Placeholder)
String name = txtName.getText().trim();
String phone = txtPhone.getText().trim();
if (name.isEmpty() || name.equals("Vd: Nguyen Van A")) {
JOptionPane.showMessageDialog(this, "Họ tên không được để trống!", "Lỗi", JOptionPane.ERROR_MESSAGE);
txtName.requestFocus(); // Đẩy con trỏ về lại ô Tên
return;
}
if (phone.isEmpty() || phone.equals("Vd: 0987654321")) {
JOptionPane.showMessageDialog(this, "Số điện thoại không được để trống!", "Lỗi", JOptionPane.ERROR_MESSAGE);
txtPhone.requestFocus(); // Đẩy con trỏ về lại ô Điện thoại
return;
}
JOptionPane.showMessageDialog(this, "Gửi thành công:\n- Tên: " + name + "\n- SĐT: " + phone);
});
// ==========================================
// 3. ĐẶT CON TRỞ VỀ NÚT SUBMIT LÚC MỚI MỞ LÊN
// ==========================================
// Mặc định Swing sẽ focus vào ô Text đầu tiên, làm mất chữ Placeholder của ô đó.
// Ta yêu cầu Focus rơi vào Panel hoặc Button để bảo toàn giao diện ban đầu.
SwingUtilities.invokeLater(() -> btnSubmit.requestFocus());
}
// ==========================================
// HÀM TIỆN ÍCH: TẠO PLACEHOLDER CỰC MẠNH
// ==========================================
private void setupPlaceholder(JTextField textField, String placeholder) {
// 1. Giao diện mặc định ban đầu
textField.setText(placeholder);
textField.setForeground(Color.GRAY);
textField.setFont(new Font("Arial", Font.ITALIC, 14));
// 2. Gắn FocusListener để thay đổi động
textField.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
// Đổi nền khi đang Focus (Highlight)
textField.setBackground(new Color(240, 248, 255)); // Xanh lơ nhạt
// Nếu chữ trong ô ĐÚNG LÀ CHỮ PLACEHOLDER -> Xóa đi để người dùng gõ
if (textField.getText().equals(placeholder)) {
textField.setText("");
textField.setForeground(Color.BLACK); // Trả lại chữ đen
textField.setFont(new Font("Arial", Font.PLAIN, 14)); // Trả lại font thường
}
}
@Override
public void focusLost(FocusEvent e) {
// Trả lại màu nền trắng
textField.setBackground(Color.WHITE);
// Nếu người dùng không gõ gì, hoặc gõ xong lại xóa trắng -> Phục hồi Placeholder
if (textField.getText().trim().isEmpty()) {
textField.setForeground(Color.GRAY);
textField.setFont(new Font("Arial", Font.ITALIC, 14));
textField.setText(placeholder);
}
}
});
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new FocusListenerDemo().setVisible(true));
}
}
Hãy copy đoạn code này và chạy. Bạn sẽ thấy ô text hiển thị chữ gợi ý màu xám nghiêng cực kỳ chuyên nghiệp. Khi click vào, chữ xám tự mất đi, nền sáng lên. Click ra ngoài mà chưa gõ gì, chữ xám lại hiện về. Đây là tiêu chuẩn thiết kế UI bắt buộc phải có cho các ứng dụng hiện đại!
All rights reserved