إدارة التركيز والانتقال بين الحقول
إدارة التركيز والانتقال بين الحقول
في نماذج Flutter الاحترافية، التحكم في الحقل النشط في أي لحظة لا يقل أهمية عن التقاط البيانات ذاتها. تتيح لك إدارة التركيز نقل مؤشر لوحة المفاتيح برمجياً من حقل TextFormField إلى التالي، وإخفاء لوحة المفاتيح عند الطلب، والاستجابة لأحداث تغيير التركيز — كل ذلك دون أي إيماءة من المستخدم سوى النقر على زر التالي أو تم في لوحة المفاتيح.
يكشف Flutter هذا النظام من خلال فئتين متكاملتين: FocusNode، التي تمثل وحدة التركيز المرتبطة بودجت إدخال واحد، وFocusScope، المدير المحدد بنطاق الشجرة الذي يوجه طلبات التركيز. معاً يمنحانك تحكماً دقيقاً في تدفق لوحة المفاتيح بالكامل في نموذجك.
فهم FocusNode
FocusNode كائن طويل العمر يجب إنشاؤه في initState وتحريره في dispose. يحمل بيانات وصفية حول ما إذا كان ودجته يمتلك التركيز حاليًا، ويتيح لك إرفاق مستمعين يُطلَقون كلما اكتُسب التركيز أو فُقد.
إنشاء FocusNodes وتحريرها
class RegistrationFormState extends State<RegistrationForm> {
// عقدة تركيز لكل حقل
final FocusNode _emailFocus = FocusNode();
final FocusNode _passwordFocus = FocusNode();
final FocusNode _phoneFocus = FocusNode();
@override
void initState() {
super.initState();
// اختياري: الاستجابة لتغييرات التركيز على حقل معين
_emailFocus.addListener(() {
if (!_emailFocus.hasFocus) {
// الحقل فقد التركيز — وقت مناسب للتحقق منه
_formKey.currentState?.validate();
}
});
}
@override
void dispose() {
// تحرير دائماً لتجنب تسرب الذاكرة
_emailFocus.dispose();
_passwordFocus.dispose();
_phoneFocus.dispose();
super.dispose();
}
}
FocusNode داخل طريقة build أبداً. كل إعادة بناء ستنشئ نسخة جديدة، مما يسبب تسرب في الذاكرة وسلوكاً غير متوقع للتركيز. خصص دائماً في initState وحرر في dispose.ربط FocusNodes بـ TextFormFields
مرر العقدة إلى المعامل focusNode في TextFormField. استخدم textInputAction لضبط زر إجراء لوحة المفاتيح (TextInputAction.next للحقول الوسيطة، وTextInputAction.done للأخير). اربط رد النداء onFieldSubmitted لتحريك التركيز للأمام.
ربط ثلاثة حقول بزر التالي في لوحة المفاتيح
final _formKey = GlobalKey<FormState>();
final _emailFocus = FocusNode();
final _passwordFocus = FocusNode();
final _phoneFocus = FocusNode();
// مساعد: نقل التركيز إلى العقدة التالية
void _fieldNext(BuildContext ctx, FocusNode next) {
FocusScope.of(ctx).requestFocus(next);
}
// مساعد: إغلاق لوحة المفاتيح كلياً
void _fieldDone(BuildContext ctx) {
FocusScope.of(ctx).unfocus();
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
focusNode: _emailFocus,
decoration: const InputDecoration(labelText: 'البريد الإلكتروني'),
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next, // يعرض التالي
onFieldSubmitted: (_) =>
_fieldNext(context, _passwordFocus), // تحريك للأمام
),
TextFormField(
focusNode: _passwordFocus,
decoration: const InputDecoration(labelText: 'كلمة المرور'),
obscureText: true,
textInputAction: TextInputAction.next,
onFieldSubmitted: (_) =>
_fieldNext(context, _phoneFocus),
),
TextFormField(
focusNode: _phoneFocus,
decoration: const InputDecoration(labelText: 'رقم الهاتف'),
keyboardType: TextInputType.phone,
textInputAction: TextInputAction.done, // يعرض تم
onFieldSubmitted: (_) => _fieldDone(context), // إخفاء لوحة المفاتيح
),
],
),
);
}
FocusScope.of(context).requestFocus()
يتجوّل FocusScope.of(context) عبر شجرة الودجات للأعلى بحثاً عن أقرب FocusScopeNode يملك السياق المعطى. استدعاء .requestFocus(node) عليه يخبر نظام تركيز Flutter بنقل التركيز الأساسي إلى تلك العقدة، مما يأمر المنصة بتحريك مؤشر لوحة المفاتيح البرمجية إلى الحقل المقابل.
requestFocus(node)— نقل التركيز إلىFocusNodeمحددunfocus()— إزالة التركيز من جميع الحقول وإخفاء لوحة المفاتيحnextFocus()— التقدم إلى الودجت القابل للتركيز التالي في ترتيب الاجتياز (أقل صراحة)hasFocusعلى عقدة — التحقق مما إذا كان الحقل نشطاً حالياً
requestFocus(specificNode) على nextFocus() في النماذج الإنتاجية. يتبع nextFocus() ترتيب شجرة الودجات، مما قد يقفز إلى ودجات غير متوقعة (أزرار، مربعات اختيار) إذا كان تخطيطك معقداً. المراجع الصريحة للعقد تكون دائماً متوقعة.التركيز البرمجي من الأزرار أو التحقق
لست مقيداً بردود نداء إجراء لوحة المفاتيح. أي تفاعل للمستخدم — النقر على زر، أو إنهاء استدعاء غير متزامن، أو اكتشاف خطأ في التحقق — يمكنه تحويل التركيز برمجياً:
القفز إلى أول حقل غير صالح بعد الإرسال
void _submit() {
// إخفاء لوحة المفاتيح أولاً
FocusScope.of(context).unfocus();
if (!(_formKey.currentState?.validate() ?? false)) {
// إعادة تركيز حقل البريد الإلكتروني حتى يرى المستخدم الخطأ الأول
FocusScope.of(context).requestFocus(_emailFocus);
return;
}
_formKey.currentState!.save();
// المتابعة ببيانات النموذج ...
}
AutofillHints وسلسلة التركيز
يُحسّن الجمع بين autofillHints وسلسلة تركيز مرتبة جيداً تجربة المستخدم بشكل كبير. عندما يملأ الإكمال التلقائي حقلاً، يتبع نقر التالي على لوحة المفاتيح مع ذلك سلسلة FocusNode الصريحة الخاصة بك، لذا تتكامل الميزتان بنظافة. اضبط دائماً autofillHints على حقول البريد الإلكتروني وكلمة المرور والهاتف في تدفقات التسجيل أو تسجيل الدخول.
requestFocus أثناء استدعاء build سيرمي خطأ setState called during build. اجدول دائماً تغييرات التركيز داخل ردود النداء (onFieldSubmitted، onPressed، addPostFrameCallback) — لا تضعها مباشرة في build أبداً.الخلاصة
تحول إدارة التركيز مجموعة أساسية من حقول الإدخال النصي إلى نموذج أنيق وسهل الاستخدام مع لوحة المفاتيح. النقاط الرئيسية التي يجب تذكرها هي:
- خصص
FocusNodeواحدة لكل حقل فيinitState؛ حررها جميعاً فيdispose. - اضبط
textInputAction: TextInputAction.nextعلى كل حقل ما عدا الأخير الذي يستخدمTextInputAction.done. - في
onFieldSubmitted، استدعFocusScope.of(context).requestFocus(nextNode)لربط الحقول. - استخدم
FocusScope.of(context).unfocus()لإخفاء لوحة المفاتيح عند إرسال النموذج. - لا تنشئ عقداً في
build، ولا تستدعrequestFocusأثناء إطار بناء.