بناء عناصر FormField مخصصة
بناء عناصر FormField مخصصة
تُعدّ عناصر TextFormField وDropdownButtonFormField المدمجة في Flutter مريحةً للاستخدام، غير أن التطبيقات الحقيقية تحتاج في الغالب إلى عناصر إدخال مخصصة — كمحدد التقييم بالنجوم، أو منتقي ألوان، أو محرر وسوم — يجب أن تشارك في دورة الحفظ والتحقق الخاصة بـ Form. وتتيح لك الفئة الأساسية العامة FormField<T> تغليف أي عنصر واجهة مستخدم اعتباطي وجعله عضواً كامل الحقوق في النموذج.
كيف يعمل FormField<T>
يحتفظ كل FormField بـ FormFieldState<T> الخاص به، الذي يخزّن ثلاثة أشياء:
- القيمة الحالية من النوع
T(مثلintلعدد النجوم). - رسالة خطأ (
String?) ينتجها استدعاءvalidator. - علامة تعديل تتيح للحقل معرفة ما إذا كان المستخدم قد تفاعل معه.
حين يستدعي Form المحيط الدالةَ FormState.validate()، فإنه يمرّ على كل FormFieldState مسجّل ويستدعي دالة validator الخاصة به. وعند استدعاء FormState.save()، يُطلق استدعاء onSaved لكل حقل. يحصل عنصرك المخصص على كليهما تلقائياً بمجرد أن يمتد من FormField<T>.
validate() أو save() على الحقول الفردية — بل تستدعيها على FormState الأب (المُسترجَع عبر _formKey.currentState!). يقوم Flutter بإيصال تلك الاستدعاءات تلقائياً إلى كل FormField في الشجرة الفرعية.توقيع FormFieldBuilder
المعامل المطلوب builder في مُنشئ FormField له هذا التوقيع:
// يستقبل الباني حالة FormFieldState الحية ويُعيد Widget.
// field.value — القيمة الحالية من النوع T
// field.errorText — null إذا كان صالحاً، سلسلة نصية إذا كان غير صالح
// field.didChange(newValue) — استدعِها لتحديث القيمة وإطلاق إعادة البناء
Widget Function(FormFieldState<T> field)
داخل الباني يمكنك عرض أي واجهة مستخدم تريدها، وربط أحداث تفاعل المستخدم بـ field.didChange()، وعرض field.errorText اختيارياً أسفل العنصر. هذا النمط الوحيد هو كل ما تحتاجه.
المثال الأول — FormField لاختيار تقييم النجوم
يتيح العنصر التالي للمستخدم اختيار من 1 إلى 5 نجوم ويندمج مباشرة في Form:
class StarRatingFormField extends FormField<int> {
StarRatingFormField({
super.key,
int initialValue = 0,
super.onSaved, // FormFieldSetter<int>? — يُستدعى بواسطة Form.save()
super.validator, // FormFieldValidator<int>? — يُستدعى بواسطة Form.validate()
super.autovalidateMode,
}) : super(
initialValue: initialValue,
builder: (FormFieldState<int> field) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(5, (index) {
final starNumber = index + 1;
return IconButton(
icon: Icon(
starNumber <= (field.value ?? 0)
? Icons.star
: Icons.star_border,
color: Colors.amber,
),
onPressed: () => field.didChange(starNumber),
);
}),
),
// عرض خطأ التحقق أسفل النجوم
if (field.hasError)
Padding(
padding: const EdgeInsets.only(left: 12, top: 4),
child: Text(
field.errorText!,
style: TextStyle(
color: Theme.of(field.context).colorScheme.error,
fontSize: 12,
),
),
),
],
);
},
);
}
توصيل الحقل المخصص بـ Form
الاستخدام مطابق تماماً لـ TextFormField. مرّر validator وonSaved، ثم استدعِ _formKey.currentState!.validate() وsave() من زر الإرسال:
class ReviewForm extends StatefulWidget {
const ReviewForm({super.key});
@override
State<ReviewForm> createState() => _ReviewFormState();
}
class _ReviewFormState extends State<ReviewForm> {
final _formKey = GlobalKey<FormState>();
int _savedRating = 0;
void _submit() {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('التقييم المحفوظ: $_savedRating نجوم')),
);
}
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('قيّم هذا المنتج'),
StarRatingFormField(
validator: (value) {
if (value == null || value == 0) {
return 'الرجاء اختيار نجمة واحدة على الأقل.';
}
return null; // صالح
},
onSaved: (value) => _savedRating = value ?? 0,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _submit,
child: const Text('إرسال'),
),
],
),
);
}
}
AutovalidateMode وإعادة الضبط
مرّر autovalidateMode: AutovalidateMode.onUserInteraction للتحقق عند كل نقرة على نجمة في الوقت الفعلي. يستعيد FormFieldState.reset() الموروث الحقلَ إلى initialValue حين يستدعي النموذج الأب FormState.reset() — تحصل على هذا السلوك مجاناً دون كتابة أي كود إضافي.
Form، احتفظ بمرجع لحالة الحقل عبر GlobalKey<FormFieldState<int>>، تماماً كما تفعل مع TextEditingController.setState() الخام داخل باني FormField — استدعِ دائماً field.didChange(newValue). يتجاوز setState المباشر آليات FormFieldState، ولن يرى النموذج القيمة المحدَّثة عند استدعاء save() أو validate().ملخص
تُعدّ عناصر FormField<T> المخصصة الطريقة الصحيحة والمتوافقة مع مبادئ Flutter لدمج أي إدخال اعتباطي في دورة الحياة المتعلقة بالحفظ والتحقق. الوصفة هي:
- امتدّ من
FormField<T>مع معامل النوع المطابق لبياناتك. - اقبل
onSavedوvalidatorوinitialValueفي المُنشئ ومرّرها إلىsuper. - في
builder، اعرض واجهة المستخدم باستخدامfield.valueواستدعِfield.didChange()عند تفاعل المستخدم. - اعرض
field.errorTextحين تكونfield.hasErrorصحيحة. - دعِ
Formالأب يتولى التحقق والحفظ — لا تستدعيهما على الحقل مباشرة.