عناصر الاختيار في Flutter: Checkbox و Switch و Radio و Slider و Dropdown
ليس كل إدخال كتابةً بلوحة المفاتيح. كثيراً ما يكون الأسهل أن يختار المستخدم: يفعّل الإشعارات بمفتاح، ويحدد المقاس من ثلاثة اختيارات، ويسحب شريطاً لضبط مستوى الصوت، ويختار مدينته من قائمة. في هذا الدرس ستتعلم عناصر الاختيار الجاهزة، ومتى تستخدم كل واحد منها.
القاعدة المشتركة
Section titled “القاعدة المشتركة”كل عناصر الاختيار تعمل بنفس النمط الذي تعلمته في StatefulWidget:
- متغير في الحالة يحمل القيمة الحالية.
- العنصر يعرض هذه القيمة عبر
value. - عند تغيير المستخدم لها تُستدعى
onChanged، وفيها تحدّث المتغير داخلsetState.
العنصر نفسه لا يحفظ قيمته. إذا نسيت setState فسيبدو العنصر مجمداً لا يستجيب.
Checkbox: مربع الاختيار
Section titled “Checkbox: مربع الاختيار”لاختيارات مستقلة يمكن تفعيل أكثر من واحد منها: «أوافق على الشروط»، «تذكرني».
CheckboxListTile( title: const Text('أرسل لي العروض بالبريد'), value: _offers, onChanged: (value) { setState(() { _offers = value ?? false; }); },)CheckboxListTile يجمع المربع مع عنوانه في صف واحد، والضغط على أي مكان في الصف يبدّل القيمة. وهذا أسهل على الإصبع من مربع صغير وحده. ويوجد Checkbox المجرد إذا أردت المربع فقط.
Switch: المفتاح
Section titled “Switch: المفتاح”لإعداد يعمل أو يتوقف فوراً: الوضع الليلي، الإشعارات، البلوتوث.
SwitchListTile( title: const Text('الإشعارات'), subtitle: const Text('تنبيهات الرسائل الجديدة'), secondary: const Icon(Icons.notifications), value: _notifications, onChanged: (value) { setState(() { _notifications = value; }); },)Radio: اختيار واحد من مجموعة
Section titled “Radio: اختيار واحد من مجموعة”عندما يجب اختيار واحد فقط من عدة اختيارات: المقاس، طريقة الدفع. سُميت بهذا الاسم تشبيهاً بأزرار الراديو القديم: ضغط زر يرفع الزر السابق.
RadioGroup<String>( groupValue: _size, onChanged: (value) { setState(() { _size = value ?? 'M'; }); }, child: const Column( children: [ RadioListTile(value: 'S', title: Text('صغير')), RadioListTile(value: 'M', title: Text('متوسط')), RadioListTile(value: 'L', title: Text('كبير')), ], ),)RadioGroupيلف الاختيارات كلها، ويحمل القيمة المختارة حالياً فيgroupValue.- كل
RadioListTileلهvalueخاصة به. الزر الذي تساوي قيمتهgroupValueيظهر محدداً. <String>هو نوع القيم. يمكن أن يكونintأوenumأيضاً.
Slider: شريط القيمة
Section titled “Slider: شريط القيمة”لاختيار رقم من مدى بالسحب: مستوى الصوت، حجم الخط، الحد الأقصى للسعر.
Slider( value: _fontSize, min: 12, max: 32, divisions: 10, label: _fontSize.round().toString(), onChanged: (value) { setState(() { _fontSize = value; }); },)minوmax: حدود المدى. والقيمة من نوعdouble.divisions: عدد التقسيمات. بدونها يتحرك الشريط بسلاسة على أي قيمة، ومعها يقفز بين نقاط محددة. هنا المدى 20 مقسم على 10، فالقفزة 2.label: فقاعة فوق الإصبع تعرض القيمة أثناء السحب، وتعمل فقط معdivisions.
القائمة المنسدلة: DropdownMenu
Section titled “القائمة المنسدلة: DropdownMenu”عندما تزيد الاختيارات على خمسة تقريباً، أزرار الراديو تأخذ مساحة كبيرة. القائمة المنسدلة تعرض الاختيار الحالي فقط، وتفتح الباقي عند الضغط.
DropdownMenu<String>( initialSelection: _city, label: const Text('المدينة'), onSelected: (value) { setState(() { _city = value ?? _city; }); }, dropdownMenuEntries: const [ DropdownMenuEntry(value: 'القاهرة', label: 'القاهرة'), DropdownMenuEntry(value: 'الإسكندرية', label: 'الإسكندرية'), DropdownMenuEntry(value: 'طنطا', label: 'طنطا'), DropdownMenuEntry(value: 'أسوان', label: 'أسوان'), ],)كل DropdownMenuEntry له value تُحفظ في الكود و label يراه المستخدم. وقد يختلفان: مثلاً القيمة رقم المدينة في قاعدة البيانات، والعنوان اسمها.
| العنصر | متى تستخدمه |
|---|---|
Checkbox |
اختيارات مستقلة داخل نموذج |
Switch |
إعداد يتغير فوراً |
Radio |
واحد من 2 إلى 5 اختيارات ظاهرة كلها |
Slider |
رقم تقريبي من مدى |
DropdownMenu |
واحد من اختيارات كثيرة |
اختيار التاريخ: showDatePicker
Section titled “اختيار التاريخ: showDatePicker”كتابة التاريخ يدوياً مملة ومليئة بالأخطاء. Flutter يوفر نافذة تقويم جاهزة:
Future<void> _pickDate() async { final now = DateTime.now(); final picked = await showDatePicker( context: context, initialDate: now, firstDate: DateTime(now.year - 1), lastDate: DateTime(now.year + 1), ); if (picked != null) { setState(() { _date = picked; }); }}showDatePickerدالةasync: تفتح التقويم، وتنتظر حتى يختار المستخدم.firstDateوlastDate: أول وآخر تاريخ مسموح باختياره.- النتيجة من نوع
DateTime?: تكونnullإذا أغلق المستخدم النافذة بدون اختيار. لذلك نفحصها بـif.
وللوقت يوجد showTimePicker بنفس الفكرة.
المشروع: شاشة إعدادات
Section titled “المشروع: شاشة إعدادات”class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key});
@override State<SettingsScreen> createState() => _SettingsScreenState();}
class _SettingsScreenState extends State<SettingsScreen> { bool _notifications = true; bool _offers = false; String _size = 'M'; double _fontSize = 18; String _city = 'القاهرة';
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('الإعدادات')), body: ListView( children: [ SwitchListTile( title: const Text('الإشعارات'), value: _notifications, onChanged: (value) => setState(() => _notifications = value), ), CheckboxListTile( title: const Text('أرسل لي العروض بالبريد'), value: _offers, onChanged: (value) => setState(() => _offers = value ?? false), ), const Divider(), const ListTile(title: Text('حجم الخط المفضل')), RadioGroup<String>( groupValue: _size, onChanged: (value) => setState(() => _size = value ?? 'M'), child: const Column( children: [ RadioListTile(value: 'S', title: Text('صغير')), RadioListTile(value: 'M', title: Text('متوسط')), RadioListTile(value: 'L', title: Text('كبير')), ], ), ), Slider( value: _fontSize, min: 12, max: 32, divisions: 10, label: _fontSize.round().toString(), onChanged: (value) => setState(() => _fontSize = value), ), Padding( padding: const EdgeInsets.all(16), child: Text( 'معاينة حجم الخط', style: TextStyle(fontSize: _fontSize), ), ), Padding( padding: const EdgeInsets.all(16), child: DropdownMenu<String>( initialSelection: _city, label: const Text('المدينة'), onSelected: (value) => setState(() => _city = value ?? _city), dropdownMenuEntries: const [ DropdownMenuEntry(value: 'القاهرة', label: 'القاهرة'), DropdownMenuEntry(value: 'الإسكندرية', label: 'الإسكندرية'), DropdownMenuEntry(value: 'طنطا', label: 'طنطا'), ], ), ), ], ), ); }}الشاشة قائمة تتمرر فيها كل العناصر. حرّك الشريط وراقب نص المعاينة يكبر ويصغر مباشرة. ولاحظ الصيغة المختصرة setState(() => _notifications = value): عندما يكون التحديث سطراً واحداً نكتبه بدالة سهمية.
جرّب بنفسك
Section titled “جرّب بنفسك”- شغّل شاشة الإعدادات وجرّب كل عنصر.
- أضف مفتاحاً للوضع الليلي يغيّر
backgroundColorللشاشة بين الأبيض والرمادي الداكن. - أضف زراً يفتح
showDatePickerويعرض التاريخ المختار تحته. - تحدٍّ: اجعل أزرار الراديو تتحكم فعلاً في حجم نص المعاينة: 14 و 18 و 24.
أخطاء شائعة
Section titled “أخطاء شائعة”- نسيان setState داخل onChanged: العنصر يبدو مجمداً لأن قيمته لم تتغير في الحالة.
- قيمة Slider خارج المدى: إذا كانت
valueأصغر منminأو أكبر منmaxيتوقف التطبيق بخطأ. تأكد من القيمة الابتدائية. - تجاهل null في نتيجة showDatePicker: إذا أغلق المستخدم النافذة ترجع
null، واستخدامها مباشرة يسبب خطأ.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
أي عنصر هو الأنسب لتفعيل الوضع الليلي في شاشة الإعدادات؟
Switch مخصص للإعدادات التي لها حالتان وتتغير فوراً عند الضغط، مثل مفتاح النور.
سؤال
ما وظيفة divisions في Slider؟
بدون divisions يتحرك المؤشر بسلاسة، ومعها يتوقف عند نقاط متساوية البعد، وتظهر فقاعة label.
الخلاصة
Section titled “الخلاصة”- كل عناصر الاختيار تعمل بنمط واحد:
valueللعرض وonChangedمعsetStateللتحديث. Checkboxداخل النماذج، وSwitchللإعدادات الفورية.RadioGroupلاختيار واحد من قليل، وDropdownMenuلاختيار واحد من كثير.Sliderلرقم من مدى، معdivisionsوlabel.showDatePickerدالةasyncتُرجعDateTime?، فافحصnull.