منسقات الإدخال: تقييد النص وتشكيله
منسقات الإدخال: تقييد النص وتشكيله
تقبل كل من TextField وTextFormField في Flutter معاملًا اسمه inputFormatters — وهو قائمة من كائنات TextInputFormatter تعترض كل ضغطة مفتاح وتستطيع تصفية القيمة أو استبدالها أو إعادة تشكيلها قبل أن تصل إلى مخزن العرض الخاص بالحقل. يمنحك هذا تحكمًا دقيقًا وفوريًا في ما يمكن للمستخدم كتابته، بما يفوق بكثير ما يمكن أن يوفره keyboardType وحده.
TextEditingValue القديمة والقيمة الجديدة المقترحة، وتُعيد القيمة التي ينبغي استخدامها فعلًا. إعادة القيمة القديمة يرفض التغيير بفاعلية.FilteringTextInputFormatter
يُعدّ FilteringTextInputFormatter أكثر المنسقات المدمجة استخدامًا. يعتمد على RegExp إما للسماح بالأحرف (القائمة البيضاء) أو لرفضها (القائمة السوداء).
FilteringTextInputFormatter.allow(pattern)— يحتفظ فقط بالأحرف المطابقة للنمط.FilteringTextInputFormatter.deny(pattern)— يحذف أي حرف يطابق النمط.- يقبل كلا المُنشِئَين معاملًا اختياريًا
replacementStringلاستبدال الأحرف المحذوفة (القيمة الافتراضية هي السلسلة الفارغة). - يوفر Flutter مُنشِئَين مُسمَّيَين جاهزَين:
FilteringTextInputFormatter.digitsOnlyوFilteringTextInputFormatter.singleLineFormatter.
أمثلة شائعة على FilteringTextInputFormatter
import 'package:flutter/services.dart';
// السماح بالأرقام فقط (مثل FilteringTextInputFormatter.digitsOnly)
TextField(
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
],
keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: 'العمر'),
),
// السماح بالحروف والمسافات فقط (بلا أرقام أو رموز)
TextField(
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z-ۿ ]')),
],
decoration: const InputDecoration(labelText: 'الاسم الكامل'),
),
// رفض الأحرف الخاصة (نهج القائمة السوداء)
TextField(
inputFormatters: [
FilteringTextInputFormatter.deny(RegExp(r'[!@#\$%^&*()]')),
],
decoration: const InputDecoration(labelText: 'اسم المستخدم'),
),
LengthLimitingTextInputFormatter
يحدّ LengthLimitingTextInputFormatter من عدد الأحرف التي يقبلها الحقل. وبينما يمكنك ضبط maxLength على TextField مباشرةً، فإن استخدام المنسّق يمنحك الحدّ دون عرض عداد الأحرف أسفل الحقل — وهو خيار أنيق للتصاميم الحساسة بصريًا.
دمج منسقات متعددة
import 'package:flutter/services.dart';
// حقل OTP/PIN: أرقام فقط، بحد أقصى 6 أحرف
TextField(
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(6),
],
keyboardType: TextInputType.number,
obscureText: true,
decoration: const InputDecoration(labelText: 'أدخل رمز OTP'),
),
// اسم مستخدم أبجدي رقمي: حروف وأرقام، بحد أقصى 20 حرفًا
TextField(
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_]')),
LengthLimitingTextInputFormatter(20),
],
decoration: const InputDecoration(labelText: 'اسم المستخدم (20 حرفًا كحد أقصى)'),
),
LengthLimitingTextInputFormatter بعد منسّق التصفية في القائمة. تُنفَّذ المنسقات بالترتيب؛ وإن عمل منسّق الطول أولًا فقد يحجب أحرفًا صالحة قبل أن يتمكن الفلتر من تقييمها — وإن كان كلا الترتيبين يعمل عمليًا في معظم الحالات، فإن جعل الترتيب مقصودًا يُبقي الكود مقروءًا.كتابة TextInputFormatter مخصص
للتشكيل الأكثر تعقيدًا — كإدراج شرطات في رقم هاتف، أو إضافة خطوط مائلة لتاريخ، أو تحويل الحرف الأول إلى حرف كبير — يمكنك توسيع TextInputFormatter وتجاوز الدالة الوحيدة المطلوبة formatEditUpdate.
توقيع الدالة هو:
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue)
تستلم القيمتين السابقة والمقترحة (بما فيها موضع المؤشر) ويجب أن تُعيد TextEditingValue النهائية — متضمنةً موضع مؤشر محدّثًا لمنعه من القفز بشكل غير متوقع.
منسّق رقم هاتف مخصص (###-###-####)
import 'package:flutter/services.dart';
/// ينسّق رقم هاتف أمريكي مكوّن من 10 أرقام بصيغة ###-###-####
/// أثناء كتابة المستخدم.
class PhoneNumberFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
// احذف كل حرف ليس رقمًا أولًا
final digitsOnly = newValue.text.replaceAll(RegExp(r'[^0-9]'), '');
// حدّ بـ 10 أرقام
final capped = digitsOnly.length > 10
? digitsOnly.substring(0, 10)
: digitsOnly;
// ابنِ السلسلة المُقنَّعة
final buffer = StringBuffer();
for (int i = 0; i < capped.length; i++) {
if (i == 3 || i == 6) buffer.write('-');
buffer.write(capped[i]);
}
final formatted = buffer.toString();
return TextEditingValue(
text: formatted,
// ضع المؤشر في نهاية السلسلة المنسّقة
selection: TextSelection.collapsed(offset: formatted.length),
);
}
}
// الاستخدام داخل ودجت:
TextField(
inputFormatters: [PhoneNumberFormatter()],
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
labelText: 'رقم الهاتف',
hintText: '555-867-5309',
),
)
selection (موضع المؤشر) في القيمة المُعادة من منسّقك المخصص. إذا أعدت TextEditingValue(text: formatted) فقط دون selection، سيقفز المؤشر إلى الإزاحة 0 عند كل ضغطة مفتاح، مما يجعل الحقل شبه غير قابل للاستخدام.مثال: منسّق رقم بطاقة الائتمان
نمط شائع آخر هو تجميع الأرقام مع مسافات — يُستخدم لأرقام بطاقات الائتمان (#### #### #### ####). المنطق مطابق لمنسّق الهاتف لكن بقواعد تجميع مختلفة.
منسّق بطاقة الائتمان (#### #### #### ####)
class CreditCardFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
final digits = newValue.text.replaceAll(RegExp(r'[^0-9]'), '');
final capped = digits.length > 16 ? digits.substring(0, 16) : digits;
final buffer = StringBuffer();
for (int i = 0; i < capped.length; i++) {
if (i != 0 && i % 4 == 0) buffer.write(' ');
buffer.write(capped[i]);
}
final formatted = buffer.toString();
return TextEditingValue(
text: formatted,
selection: TextSelection.collapsed(offset: formatted.length),
);
}
}
ملخص
منسقات الإدخال أداة قوية وخفيفة لفرض سلامة البيانات على مستوى واجهة المستخدم:
- استخدم
FilteringTextInputFormatter.allowلإدراج الأحرف المقبولة ضمن قائمة بيضاء باستخدامRegExp. - استخدم
FilteringTextInputFormatter.denyلرفض الأحرف غير المرغوب فيها. - استخدم
LengthLimitingTextInputFormatterلتحديد طول الحقل دون عرض عداد. - وسّع
TextInputFormatterوتجاوزformatEditUpdateللإخفاء المتقدم (هواتف، تواريخ، بطاقات ائتمان). - أعد دائمًا
TextEditingValueكاملة — تتضمنselectionصالحًا — من المنسقات المخصصة.
TextInputFormatter يغطي كل متطلبات تشكيل النص التي ستواجهها في تطبيقات Flutter الإنتاجية.