TextWatcher یک رابط اندروید است که به شما امکان میدهد تغییرات متن را در EditText و سایر TextViewها در زمان واقعی ردیابی کنید. توسعهدهنده در سه مرحله اعلان دریافت میکند: قبل از تغییر، در حین تغییر و پس از تغییر محتوای متنی. طبق Android Developers, 2026، TextWatcher در اکثر برنامهها برای اعتبارسنجی ورودی، شمارش کاراکترها، پیادهسازی جستجو با تکمیل خودکار و قالببندی پویای متن استفاده میشود. این رابط در فرمهایی که نیاز به واکنش فوری به هر فشردن کلید دارند ضروری است.
نکات اصلی
TextWatcher — یک رابط از بسته android.text است که برنامه را از تغییرات متن در اشیاء Editable مطلع میکند. با هر بار ورود، حذف یا جایگزینی کاراکتر، TextWatcher به ترتیب سه متد را فراخوانی میکند و اطلاعات مربوط به موقعیت تغییرات را منتقل میکند. این به توسعهدهنده اجازه میدهد فوراً — بدون دکمههای اضافی یا محرکها — به اقدامات کاربر واکنش نشان دهد.
سناریوهای اصلی استفاده شامل اعتبارسنجی فیلدها در زمان واقعی است: بررسی ایمیل با هر کاراکتر وارد شده، شمارش کاراکترهای باقیمانده در فیلد با محدودیت طول، پیادهسازی جستجو با ارسال تأخیری درخواست از طریق debounce. همچنین TextWatcher برای قالببندی ورودی استفاده میشود — مثلاً قرار دادن خودکار فاصله در شماره تلفن یا افزودن ماسک برای تاریخ.
طبق Android Developers، TextWatcher در 70٪ از برنامههایی که با فرمها کار میکنند وجود دارد. کتابخانههایی مانند Material Design Components و TextInputEditText به صورت داخلی از TextWatcher برای مدیریت حالت خطا و نمایش شمارندهها استفاده میکنند. درک نحوه کار این رابط برای هر توسعهدهنده اندروید ضروری است.
TextWatcher از طریق متد addTextChangedListener به هر شیء TextView یا EditText متصل میشود. هنگامی که کاربر کاراکتری وارد یا حذف میکند، اندروید ابتدا beforeTextChanged، سپس onTextChanged و در نهایت afterTextChanged را فراخوانی میکند. در پارامترهای هر متد دادههایی درباره محدوده در حال تغییر منتقل میشود: موقعیت شروع، تعداد کاراکترهای حذف شده و تعداد کاراکترهای اضافه شده.
درک این نکته مهم است که پس از فراخوانی afterTextChanged، شیء Editable از قبل حاوی مقدار فعلی است. بنابراین بررسی متن نهایی فیلد در afterTextChanged راحت است. تا این لحظه دادهها هنوز به طور کامل بهروز نشدهاند. توسعهدهندگان اغلب کاربرد متدها را اشتباه میگیرند و از onTextChanged برای اعتبارسنجی نهایی استفاده میکنند، در حالی که انتخاب درست afterTextChanged است.
با هر بار درج، جایگزینی یا حذف کاراکتر، زنجیره فراخوانی تضمیناً به طور کامل اجرا میشود. با این حال، اگر در داخل afterTextChanged متن تغییر کند (از طریق clear، append، insert)، TextWatcher به صورت بازگشتی فعال میشود. این شایعترین علت StackOverflowError در فرمهای اندروید است. برای جلوگیری از بازگشت از پرچم مسدودکننده استفاده میشود.
هر یک از سه متد نقش خود را در چرخه حیات تغییر متن ایفا میکند. متد 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 بهروزرسانی نشانگر پیچیدگی رمز عبور را در زمان واقعی امکانپذیر میکند که بر نرخ تبدیل ثبتنام تأثیر مثبت دارد.
اولین و بحرانیترین خطا — فراخوانی بازگشتی. اگر در داخل afterTextChanged متن همان EditText تغییر کند (از طریق s.clear()، s.append() یا s.insert())، TextWatcher دوباره فعال میشود. این یک حلقه بینهایت ایجاد میکند که به StackOverflowError ختم میشود. راهحل — استفاده از پرچم مسدودکننده isUpdating یا بررسی اینکه آیا متن واقعاً تغییر کرده است.
دومین مشکل رایج — نشت حافظه. TextWatcher از طریق کلاس ناشناس یک ارجاع ضمنی به Activity یا Fragment دارد. اگر listener هنگام نابودی View حذف نشود، garbage collector نمیتواند حافظه را آزاد کند. راهحل — استفاده از کامپوننتهای چرخه حیات یا فراخوانی صریح removeTextChangedListener در onDestroyView.
سومین خطا — استفاده از متد اشتباه. برخی توسعهدهندگان اعتبارسنجی نهایی را در onTextChanged انجام میدهند و منتظر afterTextChanged نمیمانند. در onTextChanged متن هنوز به طور کامل بهروز نشده است و خواندن مقدار نهایی ممکن است دادههای نادرست برگرداند. رویکرد درست — قرار دادن تمام منطق خواندن و بررسی متن نهایی در afterTextChanged.
| متد | زمان فراخوانی | کاربرد | آیا میتوان متن نهایی را خواند؟ |
|---|---|---|---|
| beforeTextChanged | قبل از تغییر | ذخیره وضعیت قبلی | بله |
| onTextChanged | در حین تغییر | لاگگیری، انیمیشن | خیر |
| afterTextChanged | پس از تغییر | اعتبارسنجی، شمارش، بهروزرسانی UI | بله |
چهارمین خطا — افزودن چندباره TextWatcher. اگر addTextChangedListener چندین بار برای یک EditText فراخوانی شده باشد، همه listenerها همان تغییر را پردازش خواهند کرد. در فرمهایی با افزودن پویای View، این منجر به تکراری شدن بررسیها و رفتار غیرقابل پیشبینی میشود. همیشه بررسی کنید که آیا listener قبلاً اضافه شده است یا از یک نمونه واحد استفاده کنید.
سوالات متداول
OnTextChanged در لحظه تغییر متن فراخوانی میشود، زمانی که کاراکترهای جدید هنوز اضافه نشدهاند. این متد برای انیمیشن و لاگگیری مناسب است. AfterTextChanged پس از اعمال کامل تغییرات فراخوانی میشود و از طریق پارامتر Editable به متن نهایی دسترسی میدهد. برای اعتبارسنجی و خواندن مقدار از afterTextChanged استفاده کنید.
از یک پرچم مسدودکننده از نوع Boolean استفاده کنید که قبل از تغییر متن در داخل afterTextChanged روی true تنظیم میشود. در ابتدای متد پرچم را بررسی کنید: اگر true است — خارج شوید. به طور جایگزین میتوان مقدار قدیم و جدید را مقایسه کرد و متن را فقط در صورت تفاوت واقعی تغییر داد.
بله، حتماً. کلاس ناشناس TextWatcher از طریق closure یک ارجاع به Activity نگه میدارد. اگر listener حذف نشود، Activity نمیتواند توسط garbage collector جمعآوری شود. همیشه removeTextChangedListener را در onDestroyView برای Fragment یا onDestroy برای Activity فراخوانی کنید.
بله، اما با احتیاط. در RecyclerView، ViewHolderها دوباره استفاده میشوند و TextWatcher از موقعیت قبلی ممکن است فعال بماند. همیشه TextWatcher قدیمی را قبل از تنظیم TextWatcher جدید در متد onBindViewHolder حذف کنید. از tagها یا فیلدهای جداگانه ViewHolder برای ذخیره ارجاع به listener استفاده کنید.
برای فیلد جستجو از afterTextChanged همراه با debounce (تأخیر) استفاده کنید. یک تایمر 300-500 میلیثانیهای پیادهسازی کنید که با هر تغییر جدید متن ریست میشود. این کار از ارسال درخواست به سرور با هر فشردن کلید جلوگیری میکند و بار API را کاهش میدهد.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید