مربعات الاختيار والمفاتيح وأنماط الاختيار المتعدد
مربعات الاختيار والمفاتيح وأنماط الاختيار المتعدد
يوفر Flutter ودجتَي Checkbox وSwitch لالتقاط المدخلات المنطقية (Boolean)، لكنهما ليسا بالافتراضي مرتبطَين بمسار التحقق الخاص بـ Form / FormField. لجعلهما عناصر نموذج من الدرجة الأولى — بحيث يشمل استدعاء _formKey.currentState!.validate() حالتيهما — يجب تغليفهما داخل FormField<bool> مخصص. يتناول هذا الدرس كيفية القيام بذلك بالضبط، ثم يمتد النمط إلى قائمة تحديد متعدد مدعومة بـ FormField<List<String>>.
لماذا لا يكفي Checkbox و Switch العاديان
لا يخزن ودجت Checkbox الخام أي حالة بنفسه؛ بل يجب عليك إدارتها باستخدام setState. هذا مقبول لأزرار التبديل البسيطة في واجهة المستخدم، لكنه يعني أن القيمة تقع خارج نطاق التحقق الخاص بالنموذج. إذا كان المستخدم مطالبًا بقبول الشروط قبل الإرسال، تحتاج إلى التحقق لتطبيق تلك القاعدة بشكل موحد مع جميع الحقول الأخرى. يحقق التغليف في FormField ذلك بشكل نظيف.
FormField<T> هي الصنف الأساسي وراء TextFormField. تقبل initialValue وvalidator وonSaved وbuilder الذي يستقبل FormFieldState<T> — مما يمنحك وصولًا كاملًا إلى القيمة الحالية ونص الخطأ وخطافات الحفظ والتحقق.بناء CheckboxFormField
الفكرة الأساسية هي تمرير builder إلى FormField<bool>. يستقبل المُنشئ FormFieldState<bool> field، يمكنك استدعاء field.didChange(newValue) عليه لتحديث الحالة الداخلية للنموذج وتشغيل التحقق.
مثال على CheckboxFormField المخصص
class CheckboxFormField extends FormField<bool> {
CheckboxFormField({
super.key,
required String label,
super.initialValue = false,
super.validator,
super.onSaved,
super.autovalidateMode,
}) : super(
builder: (FormFieldState<bool> field) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Checkbox(
value: field.value ?? false,
onChanged: (bool? checked) {
field.didChange(checked);
},
),
Flexible(child: Text(label)),
],
),
if (field.hasError)
Padding(
padding: const EdgeInsets.only(left: 12),
child: Text(
field.errorText!,
style: const TextStyle(
color: Colors.red,
fontSize: 12,
),
),
),
],
);
},
);
}
// الاستخدام داخل Form:
CheckboxFormField(
label: 'أوافق على الشروط والأحكام',
validator: (value) {
if (value != true) return 'يجب قبول الشروط للمتابعة.';
return null;
},
onSaved: (value) => _agreedToTerms = value ?? false,
)
field.didChange(newValue) داخل استدعاء الودجت — ولا تستدعِ setState() مباشرة. يقوم didChange بتحديث القيمة الداخلية لـ FormFieldState، ويضع علامة على الحقل كـ "متسخ"، ويشغّل (عند تفعيل AutovalidateMode) إعادة التحقق الفوري.بناء SwitchFormField
النمط الخاص بـ Switch متطابق — الودجت الداخلي فقط هو الذي يتغير. يمكنك حتى إنشاء FormField منطقي عام قابل لإعادة الاستخدام يقبل معامل منشئ للتبديل بين عرض Checkbox وSwitch.
SwitchFormField مضمّن داخل Form
FormField<bool>(
initialValue: false,
validator: (value) {
if (value != true) return 'يرجى تفعيل الإشعارات للمتابعة.';
return null;
},
onSaved: (value) => _notificationsEnabled = value ?? false,
builder: (FormFieldState<bool> field) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SwitchListTile(
title: const Text('تفعيل الإشعارات الفورية'),
subtitle: const Text('مطلوب لتحديثات الطلبات'),
value: field.value ?? false,
onChanged: field.didChange,
),
if (field.hasError)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
field.errorText!,
style: const TextStyle(color: Colors.red, fontSize: 12),
),
),
],
);
},
)
نمط قائمة الاختيار المتعدد
تتيح قائمة الاختيار المتعدد للمستخدم اختيار عدة خيارات من مجموعة ثابتة. بدلًا من FormField<bool>، تستخدم FormField<List<String>>. يعرض المنشئ عمودًا من ودجات CheckboxListTile، وكل نقرة تستدعي field.didChange(updatedList) بنسخة جديدة من القائمة.
MultiSelectFormField لقائمة من السلاسل النصية
class MultiSelectFormField extends FormField<List<String>> {
MultiSelectFormField({
super.key,
required List<String> options,
String? label,
super.initialValue,
super.validator,
super.onSaved,
}) : super(
builder: (FormFieldState<List<String>> field) {
final selected = field.value ?? <String>[];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (label != null)
Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(
label,
style: const TextStyle(fontWeight: FontWeight.w600),
),
),
...options.map((option) {
return CheckboxListTile(
title: Text(option),
value: selected.contains(option),
onChanged: (bool? checked) {
final next = List<String>.from(selected);
if (checked == true) {
next.add(option);
} else {
next.remove(option);
}
field.didChange(next);
},
controlAffinity: ListTileControlAffinity.leading,
dense: true,
);
}),
if (field.hasError)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
field.errorText!,
style: const TextStyle(color: Colors.red, fontSize: 12),
),
),
],
);
},
);
}
// الاستخدام:
MultiSelectFormField(
label: 'اختر اهتماماتك (اثنان على الأقل)',
options: const ['Flutter', 'Dart', 'Firebase', 'REST APIs', 'GraphQL'],
initialValue: const [],
validator: (value) {
if (value == null || value.length < 2) {
return 'يرجى اختيار اهتمامَين على الأقل.';
}
return null;
},
onSaved: (value) => _selectedInterests = value ?? [],
)
field.didChange() — لا تُعدّل القائمة الموجودة في مكانها. يخزّن FormFieldState مرجعًا؛ التعديل المباشر يتجاوز آلية التحقق من التغييرات ولن يُعيد الحقل التحقق أو إعادة البناء بشكل صحيح. استخدم List<String>.from(selected) أو عامل الانتشار لإنشاء نسخة جديدة.التكامل مع التحقق من النموذج وحفظه
نظرًا لأن الأنماط الثلاثة (CheckboxFormField وSwitchFormField وMultiSelectFormField) تمتد من FormField، فهي تتكامل بسلاسة مع سير عمل Form القياسي:
_formKey.currentState!.validate()— يستدعيvalidatorكل حقل، بما في ذلك الحقول المنطقية وحقول القوائم_formKey.currentState!.save()— يستدعيonSavedكل حقل، ما يُعبّئ متغيرات الحالة لديك_formKey.currentState!.reset()— يُعيد جميع الحقول إلىinitialValueالخاصة بها
الخلاصة
يمنح تغليف Checkbox وSwitch في FormField<bool> مشاركةً كاملةً في التحقق من النموذج وحفظه، ما يُزيل الحاجة إلى إدارة setState موازية. يغطي تمديد هذا النمط إلى FormField<List<String>> حالة قائمة الاختيار المتعدد. القاعدة الحاسمة هي دائمًا استدعاء field.didChange() بقيمة جديدة/نسخة بدلًا من تعديل الحالة مباشرة، حتى تتتبع آلية النموذج في Flutter التغييرات بشكل صحيح.