TextWatcher là một giao diện Android cho phép theo dõi các thay đổi văn bản trong EditText và các TextView khác trong thời gian thực. Nhà phát triển nhận được thông báo ở ba giai đoạn: trước khi thay đổi, trong khi thay đổi và sau khi thay đổi nội dung văn bản. Theo Android Developers, 2026, TextWatcher được sử dụng trong hầu hết các ứng dụng để xác thực đầu vào, đếm ký tự, triển khai tìm kiếm với tự động hoàn thành và định dạng văn bản động. Giao diện này không thể thiếu trong các biểu mẫu yêu cầu phản ứng ngay lập tức với mỗi lần nhấn phím.
Những điểm chính
TextWatcher là một giao diện từ gói android.text thông báo cho ứng dụng về các thay đổi văn bản trong các đối tượng Editable. Với mỗi lần nhập, xóa hoặc thay thế ký tự, TextWatcher gọi tuần tự ba phương thức, truyền thông tin về vị trí của các thay đổi. Điều này cho phép nhà phát triển phản ứng tức thì với hành động của người dùng — mà không cần thêm nút bấm hay trình kích hoạt.
Các trường hợp sử dụng chính bao gồm xác thực trường thời gian thực: kiểm tra email khi nhập từng ký tự, đếm số ký tự còn lại trong trường có giới hạn độ dài, triển khai tìm kiếm với yêu cầu trì hoãn qua debounce. TextWatcher cũng được sử dụng để định dạng đầu vào — ví dụ: tự động chèn khoảng trắng trong số điện thoại hoặc thêm mặt nạ cho ngày tháng.
Theo Android Developers, TextWatcher có mặt trong 70% ứng dụng làm việc với biểu mẫu. Các thư viện như Material Design Components và TextInputEditText sử dụng TextWatcher nội bộ để quản lý trạng thái lỗi và hiển thị bộ đếm. Hiểu cách hoạt động của giao diện này là cần thiết cho mọi nhà phát triển Android.
TextWatcher kết nối với bất kỳ đối tượng TextView hoặc EditText nào thông qua phương thức addTextChangedListener. Khi người dùng nhập hoặc xóa một ký tự, Android gọi đầu tiên beforeTextChanged, sau đó onTextChanged, và cuối cùng là afterTextChanged. Các tham số của mỗi phương thức chứa dữ liệu về phạm vi thay đổi: vị trí bắt đầu, số ký tự bị xóa và số ký tự được thêm vào.
Điều quan trọng là hiểu rằng sau khi gọi afterTextChanged, đối tượng Editable đã chứa giá trị hiện tại. Do đó, việc kiểm tra văn bản cuối cùng của trường trong afterTextChanged là thuận tiện. Trước thời điểm đó, dữ liệu chưa được cập nhật đầy đủ. Các nhà phát triển thường nhầm lẫn mục đích của các phương thức và sử dụng onTextChanged để xác thực cuối cùng, mặc dù lựa chọn đúng là afterTextChanged.
Với mỗi lần chèn, thay thế hoặc xóa ký tự, chuỗi gọi được đảm bảo thực thi hoàn toàn. Tuy nhiên, nếu văn bản được thay đổi bên trong afterTextChanged (qua clear, append, insert), TextWatcher sẽ kích hoạt đệ quy. Đây là nguyên nhân phổ biến nhất của StackOverflowError trong các biểu mẫu Android. Để ngăn chặn đệ quy, người ta sử dụng cờ khóa.
Mỗi phương thức trong ba phương thức đóng vai trò của nó trong vòng đời thay đổi văn bản. Phương thức beforeTextChanged(CharSequence s, int start, int count, int after) được gọi trước khi áp dụng các thay đổi. Nó truyền trạng thái hiện tại của chuỗi, vị trí bắt đầu thay đổi, số ký tự bị xóa và số ký tự được thêm vào. Ở đây bạn có thể lưu giá trị trước đó hoặc kiểm tra điều kiện trước khi sửa đổi.
Phương thức onTextChanged được gọi trong quá trình thay đổi, khi các ký tự đã bị xóa nhưng các ký tự mới chưa được chèn vào. Tham số: văn bản sau khi xóa, vị trí bắt đầu, số ký tự bị xóa và số ký tự được thêm vào. Phương thức này thuận tiện cho hoạt ảnh hoặc ghi nhật ký, nhưng không phải để làm việc với văn bản cuối cùng thực tế — nó chưa được tập hợp.
Phương thức afterTextChanged là được yêu cầu nhiều nhất. Nó nhận một đối tượng Editable và được gọi sau khi các thay đổi đã được áp dụng hoàn toàn. Trong phương thức này, bạn có thể đọc giá trị cuối cùng của trường, thực hiện xác thực, cập nhật UI và sửa đổi văn bản (cẩn thận do đệ quy).
Một ví dụ thực tế — bộ đếm ký tự cho trường nhập liệu được cập nhật mỗi khi văn bản thay đổi. Phần tử như vậy thường thấy trong các biểu mẫu phản hồi, bài đăng và tin nhắn có giới hạn độ dài. Triển khai qua TextWatcher chỉ mất vài dòng và không cần thư viện bên thứ ba.
val editText = findViewById<EditText>(R.id.edit_text)
val counterText = findViewById<TextView>(R.id.counter)
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(
s: CharSequence?, start: Int,
count: Int, after: Int
) {}
override fun onTextChanged(
s: CharSequence?, start: Int,
before: Int, count: Int
) {}
override fun afterTextChanged(s: Editable?) {
val len = s?.length ?: 0
counterText.text = "$len / 200"
}
})
Trong ví dụ, phương thức afterTextChanged nhận nội dung hiện tại của trường qua tham số s kiểu Editable. Độ dài văn bản được cập nhật trong một TextView riêng biệt. Trong trường hợp này, chỉ counterText được sửa đổi, không phải EditText, do đó không có vòng lặp nào xảy ra. Với giới hạn 200 ký tự, bạn có thể chặn thêm đầu vào sau khi vượt quá.
Các phương thức beforeTextChanged và onTextChanged vẫn trống, vì trạng thái cuối cùng là đủ để đếm độ dài. Nếu bạn cần ghi nhật ký mỗi thay đổi, mã có thể được thêm vào onTextChanged. Sự linh hoạt này làm cho TextWatcher trở thành công cụ phổ quát cho mọi kịch bản nhập văn bản.
Xác thực thời gian thực cải thiện đáng kể UX: người dùng thấy lỗi ngay sau khi nhập giá trị không chính xác, thay vì sau khi nhấp vào nút gửi. TextWatcher cho phép kiểm tra tức thì email, mật khẩu, số điện thoại và các trường khác. Kết quả được hiển thị qua setError trên EditText hoặc qua TextView riêng với thông báo lỗi.
fun validateEmail(emailEditText: EditText) {
emailEditText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
val email = s?.toString () ?: ""
if (email.isNotBlank() &&
!Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
emailEditText.error = "Invalid email address"
} else {
emailEditText.error = null
}
}
override fun beforeTextChanged(...) {}
override fun onTextChanged(...) {}
})
}
Ví dụ sử dụng Patterns.EMAIL_ADDRESS tích hợp từ Android SDK để kiểm tra email. Nếu văn bản không trống và không khớp với mẫu, một lỗi được đặt trên trường qua thuộc tính error. Khi đầu vào đúng, lỗi được xóa. Điều quan trọng là không chạy xác thực trên trường trống — người dùng có thể chưa bắt đầu nhập và thông báo lỗi sẽ quá sớm.
Đối với mật khẩu và số điện thoại, biểu thức chính quy tùy chỉnh hoặc thư viện chuyên dụng được sử dụng. Ví dụ: để kiểm tra độ phức tạp của mật khẩu, bạn có thể đếm số chữ số, chữ hoa và chữ thường. TextWatcher cho phép cập nhật chỉ báo độ mạnh mật khẩu theo thời gian thực, ảnh hưởng tích cực đến chuyển đổi đăng ký.
Lỗi đầu tiên và nghiêm trọng nhất là gọi đệ quy. Nếu bạn thay đổi văn bản của cùng một EditText bên trong afterTextChanged (qua s.clear(), s.append() hoặc s.insert()), TextWatcher sẽ kích hoạt lại. Điều này tạo ra vòng lặp vô hạn kết thúc bằng StackOverflowError. Giải pháp là sử dụng cờ khóa isUpdating hoặc kiểm tra xem văn bản có thực sự thay đổi không.
Vấn đề phổ biến thứ hai là rò rỉ bộ nhớ. TextWatcher giữ một tham chiếu ngầm đến Activity hoặc Fragment thông qua một lớp ẩn danh. Nếu listener không được xóa khi View bị hủy, bộ thu gom rác không thể giải phóng bộ nhớ. Giải pháp là sử dụng các thành phần vòng đời hoặc gọi rõ ràng removeTextChangedListener trong onDestroyView.
Lỗi thứ ba là sử dụng sai phương thức. Một số nhà phát triển thực hiện xác thực cuối cùng trong onTextChanged mà không chờ afterTextChanged. Trong onTextChanged, văn bản chưa được cập nhật đầy đủ và việc đọc giá trị cuối cùng có thể trả về dữ liệu không chính xác. Cách tiếp cận đúng là tất cả logic đọc và kiểm tra văn bản cuối cùng phải nằm trong afterTextChanged.
| Phương thức | Thời điểm gọi | Mục đích | Có thể đọc văn bản cuối cùng? |
|---|---|---|---|
| beforeTextChanged | Trước khi thay đổi | Lưu trạng thái trước đó | Có |
| onTextChanged | Trong khi thay đổi | Ghi nhật ký, hoạt ảnh | Không |
| afterTextChanged | Sau khi thay đổi | Xác thực, đếm, cập nhật UI | Có |
Lỗi thứ tư là thêm TextWatcher nhiều lần. Nếu addTextChangedListener được gọi nhiều lần cho cùng một EditText, tất cả các listener sẽ xử lý cùng một thay đổi. Trong các biểu mẫu có thêm View động, điều này dẫn đến kiểm tra trùng lặp và hành vi không thể dự đoán. Luôn kiểm tra xem listener đã được thêm chưa hoặc sử dụng một phiên bản duy nhất.
Câu hỏi thường gặp
OnTextChanged được gọi tại thời điểm thay đổi văn bản khi các ký tự mới chưa được thêm vào. Phương thức này phù hợp cho hoạt ảnh và ghi nhật ký. AfterTextChanged được gọi sau khi các thay đổi đã được áp dụng đầy đủ và cung cấp quyền truy cập vào văn bản cuối cùng qua tham số Editable. Để xác thực và đọc giá trị, hãy sử dụng afterTextChanged.
Sử dụng cờ khóa kiểu Boolean, được đặt thành true trước khi thay đổi văn bản bên trong afterTextChanged. Kiểm tra cờ ở đầu phương thức: nếu true — thoát ra. Thay vào đó, bạn có thể so sánh giá trị cũ và mới và chỉ thay đổi văn bản khi có sự khác biệt thực tế.
Có, bắt buộc. Lớp ẩn danh TextWatcher giữ tham chiếu đến Activity thông qua một closure. Nếu listener không bị xóa, Activity không thể được thu gom rác. Luôn gọi removeTextChangedListener trong onDestroyView cho Fragment hoặc onDestroy cho Activity.
Có, nhưng cẩn thận. Trong RecyclerView, các ViewHolders được tái sử dụng và TextWatcher từ vị trí trước có thể vẫn hoạt động. Luôn xóa TextWatcher cũ trước khi đặt TextWatcher mới trong phương thức onBindViewHolder. Sử dụng thẻ hoặc các trường riêng của ViewHolder để lưu tham chiếu listener.
Đối với trường tìm kiếm, hãy sử dụng afterTextChanged kết hợp với debounce (độ trễ). Triển khai bộ đếm thời gian 300-500 ms được đặt lại mỗi khi văn bản thay đổi. Điều này ngăn gửi yêu cầu đến máy chủ mỗi lần nhấn phím và giảm tải API.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm