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 وتُستدعى بعد تطبيق التغييرات بالكامل. في هذه الطريقة يمكن قراءة القيمة النهائية للحقل، إجراء التحقق، تحديث واجهة المستخدم، وتعديل النص (بحذر بسبب التكرار).
مثال عملي - عداد الأحرف لحقل الإدخال الذي يتم تحديثه مع كل تغيير في النص. غالبًا ما يوجد هذا العنصر في نماذج التعليقات والمنشورات والرسائل ذات الحد الأقصى للطول. التنفيذ عبر 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 أداة عالمية لأي سيناريو إدخال نص.
التحقق في الوقت الفعلي يحسن تجربة المستخدم بشكل كبير: يرى المستخدم الخطأ فور إدخال قيمة غير صحيحة، بدلاً من رؤيته بعد النقر على زر الإرسال. يسمح 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 من خلال فئة مجهولة. إذا لم يتم إزالة المستمع عند تدمير العرض، لن يتمكن جامع القمامة من تحرير الذاكرة. الحل هو استخدام مكونات دورة الحياة أو استدعاء removeTextChangedListener صراحةً في onDestroyView.
الخطأ الثالث هو استخدام الطريقة الخاطئة. يقوم بعض المطورين بإجراء التحقق النهائي في onTextChanged، دون انتظار afterTextChanged. في onTextChanged، النص لم يتم تحديثه بالكامل بعد، وقراءة القيمة النهائية قد تعيد بيانات غير صحيحة. النهج الصحيح هو أن كل منطق قراءة النص النهائي والتحقق منه يجب أن يكون في afterTextChanged.
| الطريقة | وقت الاستدعاء | الغرض | هل يمكن قراءة النص النهائي؟ |
|---|---|---|---|
| beforeTextChanged | قبل التغيير | حفظ الحالة السابقة | نعم |
| onTextChanged | أثناء التغيير | التسجيل، الرسوم المتحركة | لا |
| afterTextChanged | بعد التغيير | التحقق، العد، تحديث واجهة المستخدم | نعم |
الخطأ الرابع هو الإضافة المتعددة لـ TextWatcher. إذا تم استدعاء addTextChangedListener عدة مرات لنفس EditText، فستقوم جميع المستمعين بمعالجة نفس التغيير. في النماذج ذات الإضافة الديناميكية للعروض، يؤدي ذلك إلى تكرار الفحوصات وسلوك غير متوقع. تحقق دائمًا مما إذا كان المستمع قد أضيف بالفعل، أو استخدم مثيلًا واحدًا.
الأسئلة الشائعة
OnTextChanged يُستدعى في لحظة تغيير النص عندما لم تتم إضافة الأحرف الجديدة بعد. هذه الطريقة مناسبة للرسوم المتحركة والتسجيل. AfterTextChanged يُستدعى بعد تطبيق التغييرات بالكامل ويتيح الوصول إلى النص النهائي عبر معامل Editable. للتحقق وقراءة القيم، استخدم afterTextChanged.
استخدم علامة قفل من نوع Boolean، يتم تعيينها إلى true قبل تغيير النص داخل afterTextChanged. تحقق من العلامة في بداية الطريقة: إذا كانت true - اخرج. بدلاً من ذلك، يمكن مقارنة القيم القديمة والجديدة وتغيير النص فقط عند وجود اختلاف فعلي.
نعم، بالتأكيد. الفئة المجهولة TextWatcher تحتفظ بمرجع إلى Activity من خلال الإغلاق. إذا لم تتم إزالة المستمع، لا يمكن لـ Activity أن يتم جمعها بواسطة جامع القمامة. استدعِ دائمًا removeTextChangedListener في onDestroyView لـ Fragment أو في onDestroy لـ Activity.
نعم، ولكن بحذر. في RecyclerView، يتم إعادة استخدام ViewHolders، وقد يظل TextWatcher من موضع سابق نشطًا. قم دائمًا بإزالة TextWatcher القديم قبل تعيين واحد جديد في طريقة onBindViewHolder. استخدم الوسوم أو حقول منفصلة في ViewHolder لتخزين مرجع المستمع.
لحقل البحث، استخدم afterTextChanged مع debounce (تأخير). قم بتنفيذ مؤقت 300-500 مللي ثانية يتم إعادة تعيينه مع كل تغيير جديد في النص. هذا يمنع إرسال طلب إلى الخادم عند كل ضغطة مفتاح ويقلل من تحميل API.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا