TextWatcher คืออินเทอร์เฟซ Android ที่ช่วยให้ติดตามการเปลี่ยนแปลงข้อความใน EditText และ TextView อื่นๆ แบบเรียลไทม์ นักพัฒนาได้รับการแจ้งเตือนในสามขั้นตอน: ก่อนการเปลี่ยนแปลง ระหว่างการเปลี่ยนแปลง และหลังการเปลี่ยนแปลงเนื้อหาข้อความ ตามข้อมูลจาก Android Developers, 2026, TextWatcher ถูกใช้ในแอปพลิเคชันส่วนใหญ่สำหรับการตรวจสอบความถูกต้องของการป้อนข้อมูล การนับตัวอักษร การใช้งานค้นหาพร้อมเติมข้อความอัตโนมัติ และการจัดรูปแบบข้อความแบบไดนามิก อินเทอร์เฟซนี้จำเป็นในฟอร์มที่ต้องการการตอบสนองทันทีต่อทุกการกดแป้นพิมพ์
ประเด็นสำคัญ
TextWatcher คืออินเทอร์เฟซจากแพ็คเกจ android.text ที่แจ้งให้แอปพลิเคชันทราบเกี่ยวกับการเปลี่ยนแปลงข้อความในออบเจ็กต์ Editable ทุกครั้งที่มีการป้อน ลบ หรือแทนที่อักขระ TextWatcher จะเรียกสามเมธอดตามลำดับ โดยส่งข้อมูลเกี่ยวกับตำแหน่งของการเปลี่ยนแปลง ทำให้ผู้พัฒนาสามารถตอบสนองต่อการกระทำของผู้ใช้ได้ทันที — โดยไม่ต้องมีปุ่มหรือทริกเกอร์เพิ่มเติม
กรณีการใช้งานหลักรวมถึง การตรวจสอบความถูกต้องของฟิลด์แบบเรียลไทม์: ตรวจสอบอีเมลขณะพิมพ์แต่ละตัวอักษร นับตัวอักษรที่เหลือในฟิลด์ที่มีข้อจำกัดความยาว การใช้งานค้นหาพร้อมคำขอที่หน่วงเวลาผ่าน debounce TextWatcher ยังใช้สำหรับจัดรูปแบบการป้อนข้อมูล เช่น การเพิ่มช่องว่างในหมายเลขโทรศัพท์โดยอัตโนมัติ หรือการเพิ่มมาสก์สำหรับวันที่
ตามข้อมูลจาก Android Developers, TextWatcher มีอยู่ใน 70% ของแอปพลิเคชันที่ทำงานกับฟอร์ม ไลบรารีอย่าง Material Design Components และ TextInputEditText ใช้ TextWatcher ภายในเพื่อจัดการสถานะข้อผิดพลาดและแสดงตัวนับ การทำความเข้าใจการทำงานของอินเทอร์เฟซนี้จำเป็นสำหรับนักพัฒนา Android ทุกคน
TextWatcher เชื่อมต่อกับออบเจ็กต์ TextView หรือ EditText ใดๆ ผ่านเมธอด addTextChangedListener เมื่อผู้ใช้พิมพ์หรือลบอักขระ Android จะเรียก beforeTextChanged ก่อน จากนั้น onTextChanged และสุดท้าย afterTextChanged พารามิเตอร์ของแต่ละเมธอดมีข้อมูลเกี่ยวกับช่วงที่เปลี่ยนแปลง: ตำแหน่งเริ่มต้น จำนวนอักขระที่ลบ และจำนวนอักขระที่เพิ่ม
สิ่งสำคัญคือต้องเข้าใจว่าหลังจากเรียก afterTextChanged ออบเจ็กต์ Editable จะมีค่าปัจจุบันอยู่แล้ว ดังนั้นการตรวจสอบข้อความสุดท้ายของฟิลด์ใน afterTextChanged จึงสะดวก ก่อนหน้านั้น ข้อมูลยังไม่ได้รับการอัปเดตอย่างสมบูรณ์ ผู้พัฒนามักสับสนวัตถุประสงค์ของเมธอดและใช้ onTextChanged สำหรับการตรวจสอบครั้งสุดท้าย แม้ว่าตัวเลือกที่ถูกต้องคือ afterTextChanged
ทุกครั้งที่มีการแทรก แทนที่ หรือลบอักขระ ห่วงโซ่การเรียก จะถูกดำเนินการอย่างสมบูรณ์ อย่างไรก็ตาม หากมีการเปลี่ยนแปลงข้อความภายใน afterTextChanged (ผ่าน clear, append, insert) TextWatcher จะถูกเรียกซ้ำ นี่คือสาเหตุที่พบบ่อยที่สุดของ StackOverflowError ในฟอร์ม Android เพื่อป้องกันการเรียกซ้ำ ใช้แฟล็กล็อค
แต่ละเมธอดในสามเมธอดมีบทบาทใน วงจรชีวิตของการเปลี่ยนแปลงข้อความ เมธอด beforeTextChanged(CharSequence s, int start, int count, int after) จะถูกเรียกก่อนที่จะใช้การเปลี่ยนแปลง โดยส่งสถานะปัจจุบันของสตริง ตำแหน่งเริ่มต้นของการเปลี่ยนแปลง จำนวนอักขระที่ถูกลบ และจำนวนที่จะเพิ่ม ที่นี่คุณสามารถบันทึกค่าก่อนหน้าหรือตรวจสอบเงื่อนไขก่อนการแก้ไข
เมธอด onTextChanged ถูกเรียกในระหว่างการเปลี่ยนแปลง เมื่ออักขระถูกลบไปแล้ว แต่ยังไม่ได้แทรกอักขระใหม่ พารามิเตอร์: ข้อความหลังการลบ ตำแหน่งเริ่มต้น จำนวนอักขระที่ลบ และจำนวนอักขระที่เพิ่ม เมธอดนี้สะดวกสำหรับแอนิเมชันหรือการบันทึก แต่ไม่เหมาะสำหรับการทำงานกับข้อความสุดท้ายจริง — ยังไม่ได้ประกอบ
เมธอด afterTextChanged เป็นที่ต้องการมากที่สุด โดยรับออบเจ็กต์ Editable และถูกเรียกหลังจากใช้การเปลี่ยนแปลงอย่างสมบูรณ์ ในเมธอดนี้คุณสามารถอ่านค่าสุดท้ายของฟิลด์ ดำเนินการตรวจสอบ อัปเดต UI และแก้ไขข้อความ (ด้วยความระมัดระวังเนื่องจากการเรียกซ้ำ)
ตัวอย่างที่ใช้งานได้จริง — ตัวนับตัวอักษร สำหรับฟิลด์ป้อนข้อมูลที่อัปเดตทุกครั้งที่มีการเปลี่ยนแปลงข้อความ องค์ประกอบดังกล่าวมักพบในฟอร์มคำติชม โพสต์ และข้อความที่มีข้อจำกัดความยาว การใช้งานผ่าน TextWatcher ใช้เวลาไม่กี่บรรทัดและไม่ต้องใช้ไลบรารีภายนอก
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"
}
})
ในตัวอย่าง เมธอด afterTextChanged รับเนื้อหาปัจจุบันของฟิลด์ผ่านพารามิเตอร์ s ชนิด Editable ความยาวข้อความถูกอัปเดตใน TextView ที่แยกต่างหาก ในกรณีนี้ เฉพาะ counterText ที่ถูกแก้ไข ไม่ใช่ EditText ดังนั้นจึงไม่มีลูปเกิดขึ้น สำหรับขีดจำกัดที่ 200 ตัวอักษร คุณสามารถบล็อกการป้อนข้อมูลเพิ่มเติมหลังจากเกิน
เมธอด beforeTextChanged และ onTextChanged ยังคงว่างเปล่า เนื่องจากสถานะสุดท้ายเพียงพอสำหรับการนับความยาว หากคุณต้องการบันทึกทุกการเปลี่ยนแปลง สามารถเพิ่มโค้ดใน onTextChanged ความยืดหยุ่นนี้ทำให้ TextWatcher เป็นเครื่องมือสากลสำหรับทุกสถานการณ์การป้อนข้อความ
การตรวจสอบแบบเรียลไทม์ ช่วยปรับปรุง UX อย่างมาก: ผู้ใช้เห็นข้อผิดพลาดทันทีหลังจากป้อนค่าที่ไม่ถูกต้อง แทนที่จะเห็นหลังจากคลิกปุ่มส่ง TextWatcher ช่วยให้ตรวจสอบอีเมล รหัสผ่าน หมายเลขโทรศัพท์ และฟิลด์อื่นๆ ได้ทันที ผลลัพธ์จะแสดงผ่าน setError บน EditText หรือผ่าน TextView ที่แยกต่างหากพร้อมข้อความแสดงข้อผิดพลาด
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(...) {}
})
}
ตัวอย่างใช้ Patterns.EMAIL_ADDRESS ในตัวจาก Android SDK เพื่อตรวจสอบอีเมล หากข้อความไม่ว่างเปล่าและไม่ตรงกับรูปแบบ ข้อผิดพลาดจะถูกตั้งค่าบนฟิลด์ผ่านพร็อพเพอร์ตี้ error เมื่อป้อนข้อมูลถูกต้อง ข้อผิดพลาดจะถูกล้าง สิ่งสำคัญคือไม่ต้องเรียกใช้การตรวจสอบบนฟิลด์ว่าง — ผู้ใช้อาจยังไม่ได้เริ่มพิมพ์ และข้อความแสดงข้อผิดพลาดจะเร็วเกินไป
สำหรับรหัสผ่านและหมายเลขโทรศัพท์ ใช้ นิพจน์ปกติแบบกำหนดเอง หรือไลบรารีเฉพาะทาง ตัวอย่างเช่น ในการตรวจสอบความซับซ้อนของรหัสผ่าน คุณสามารถนับจำนวนตัวเลข ตัวพิมพ์ใหญ่และตัวพิมพ์เล็ก TextWatcher ช่วยให้อัปเดตตัวบ่งชี้ความแข็งแกร่งของรหัสผ่านแบบเรียลไทม์ ซึ่งส่งผลดีต่อการแปลงการลงทะเบียน
ข้อผิดพลาดแรกและร้ายแรงที่สุดคือ การเรียกซ้ำ หากคุณเปลี่ยนข้อความของ EditText เดียวกันภายใน afterTextChanged (ผ่าน s.clear(), s.append() หรือ s.insert()) TextWatcher จะถูกเรียกอีกครั้ง สิ่งนี้สร้างลูปไม่มีที่สิ้นสุดซึ่งจบลงด้วย StackOverflowError วิธีแก้ไขคือใช้แฟล็กล็อค isUpdating หรือตรวจสอบว่าข้อความเปลี่ยนไปจริงหรือไม่
ปัญหาที่พบบ่อยเป็นอันดับสองคือ การรั่วไหลของหน่วยความจำ TextWatcher เก็บการอ้างอิงโดยนัยไปยัง Activity หรือ Fragment ผ่านคลาสนิรนาม หากไม่ได้ลบ Listener เมื่อ View ถูกทำลาย ตัวเก็บขยะจะไม่สามารถคืนหน่วยความจำได้ วิธีแก้ไขคือใช้คอมโพเนนต์วงจรชีวิตหรือเรียก removeTextChangedListener อย่างชัดเจนใน onDestroyView
ข้อผิดพลาดที่สามคือ การใช้เมธอดผิด นักพัฒนาบางคนดำเนินการตรวจสอบครั้งสุดท้ายใน onTextChanged โดยไม่รอ afterTextChanged ใน onTextChanged ข้อความยังไม่ได้รับการอัปเดตอย่างสมบูรณ์ และการอ่านค่าสุดท้ายอาจส่งคืนข้อมูลที่ไม่ถูกต้อง วิธีที่ถูกต้องคือตรรกะทั้งหมดสำหรับการอ่านและตรวจสอบข้อความสุดท้ายควรอยู่ใน afterTextChanged
| เมธอด | เวลาที่เรียก | วัตถุประสงค์ | อ่านข้อความสุดท้ายได้ไหม? |
|---|---|---|---|
| beforeTextChanged | ก่อนการเปลี่ยนแปลง | บันทึกสถานะก่อนหน้า | ได้ |
| onTextChanged | ระหว่างการเปลี่ยนแปลง | การบันทึก แอนิเมชัน | ไม่ได้ |
| afterTextChanged | หลังการเปลี่ยนแปลง | การตรวจสอบ การนับ การอัปเดต UI | ได้ |
ข้อผิดพลาดที่สี่คือ การเพิ่ม TextWatcher หลายครั้ง หากเรียก addTextChangedListener หลายครั้งสำหรับ EditText เดียวกัน Listener ทั้งหมดจะประมวลผลการเปลี่ยนแปลงเดียวกัน ในฟอร์มที่มีการเพิ่ม View แบบไดนามิก สิ่งนี้นำไปสู่การตรวจสอบซ้ำและพฤติกรรมที่คาดเดาไม่ได้ ตรวจสอบเสมอว่า Listener ถูกเพิ่มไปแล้วหรือไม่ หรือใช้อินสแตนซ์เดียว
คำถามที่พบบ่อย
OnTextChanged ถูกเรียกในขณะที่มีการเปลี่ยนแปลงข้อความเมื่อยังไม่ได้เพิ่มอักขระใหม่ เมธอดนี้เหมาะสำหรับแอนิเมชันและการบันทึก AfterTextChanged ถูกเรียกหลังจากใช้การเปลี่ยนแปลงอย่างสมบูรณ์และให้การเข้าถึงข้อความสุดท้ายผ่านพารามิเตอร์ Editable สำหรับการตรวจสอบและอ่านค่า ให้ใช้ afterTextChanged
ใช้ แฟล็กล็อค ชนิด Boolean ซึ่งตั้งค่าเป็น true ก่อนเปลี่ยนข้อความภายใน afterTextChanged ตรวจสอบแฟล็กที่จุดเริ่มต้นของเมธอด: ถ้า true — ให้ออก หรือเปรียบเทียบค่าเก่าและใหม่ และเปลี่ยนข้อความเมื่อมีความแตกต่างจริงเท่านั้น
ใช่ จำเป็นอย่างยิ่ง คลาสนิรนาม TextWatcher เก็บการอ้างอิงไปยัง Activity ผ่านการปิด หากไม่ลบ Listener Activity จะไม่สามารถถูกเก็บขยะได้ เรียก removeTextChangedListener ใน onDestroyView สำหรับ Fragment หรือ onDestroy สำหรับ Activity เสมอ
ได้ แต่ด้วย ความระมัดระวัง ใน RecyclerView ViewHolder จะถูกนำกลับมาใช้ใหม่ และ TextWatcher จากตำแหน่งก่อนหน้าอาจยังทำงานอยู่ ให้ลบ TextWatcher เก่าก่อนตั้งค่าใหม่ในเมธอด onBindViewHolder เสมอ ใช้แท็กหรือฟิลด์แยกของ ViewHolder เพื่อเก็บการอ้างอิง Listener
สำหรับฟิลด์ค้นหา ให้ใช้ afterTextChanged ร่วมกับ debounce (หน่วงเวลา) ใช้ตัวจับเวลา 300-500 ms ที่รีเซ็ตทุกครั้งที่มีการเปลี่ยนแปลงข้อความใหม่ ซึ่งป้องกันการส่งคำขอไปยังเซิร์ฟเวอร์ทุกครั้งที่กดแป้นพิมพ์และลดโหลด API
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม