تخطَّ إلى المحتوى

عناصر الاختيار في Flutter: Checkbox و Switch و Radio و Slider و Dropdown

الدرس 19 من 33 · Flutter

ليس كل إدخال كتابةً بلوحة المفاتيح. كثيراً ما يكون الأسهل أن يختار المستخدم: يفعّل الإشعارات بمفتاح، ويحدد المقاس من ثلاثة اختيارات، ويسحب شريطاً لضبط مستوى الصوت، ويختار مدينته من قائمة. في هذا الدرس ستتعلم عناصر الاختيار الجاهزة، ومتى تستخدم كل واحد منها.

كل عناصر الاختيار تعمل بنفس النمط الذي تعلمته في StatefulWidget:

  1. متغير في الحالة يحمل القيمة الحالية.
  2. العنصر يعرض هذه القيمة عبر value.
  3. عند تغيير المستخدم لها تُستدعى onChanged، وفيها تحدّث المتغير داخل setState.

العنصر نفسه لا يحفظ قيمته. إذا نسيت setState فسيبدو العنصر مجمداً لا يستجيب.

لاختيارات مستقلة يمكن تفعيل أكثر من واحد منها: «أوافق على الشروط»، «تذكرني».

CheckboxListTile(
title: const Text('أرسل لي العروض بالبريد'),
value: _offers,
onChanged: (value) {
setState(() {
_offers = value ?? false;
});
},
)

CheckboxListTile يجمع المربع مع عنوانه في صف واحد، والضغط على أي مكان في الصف يبدّل القيمة. وهذا أسهل على الإصبع من مربع صغير وحده. ويوجد Checkbox المجرد إذا أردت المربع فقط.

لإعداد يعمل أو يتوقف فوراً: الوضع الليلي، الإشعارات، البلوتوث.

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(
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 بنفس الفكرة.

lib/settings_screen.dart
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): عندما يكون التحديث سطراً واحداً نكتبه بدالة سهمية.

  1. شغّل شاشة الإعدادات وجرّب كل عنصر.
  2. أضف مفتاحاً للوضع الليلي يغيّر backgroundColor للشاشة بين الأبيض والرمادي الداكن.
  3. أضف زراً يفتح showDatePicker ويعرض التاريخ المختار تحته.
  4. تحدٍّ: اجعل أزرار الراديو تتحكم فعلاً في حجم نص المعاينة: 14 و 18 و 24.
  • نسيان setState داخل onChanged: العنصر يبدو مجمداً لأن قيمته لم تتغير في الحالة.
  • قيمة Slider خارج المدى: إذا كانت value أصغر من min أو أكبر من max يتوقف التطبيق بخطأ. تأكد من القيمة الابتدائية.
  • تجاهل null في نتيجة showDatePicker: إذا أغلق المستخدم النافذة ترجع null، واستخدامها مباشرة يسبب خطأ.

سؤال

أي عنصر هو الأنسب لتفعيل الوضع الليلي في شاشة الإعدادات؟

سؤال

ما وظيفة divisions في Slider؟

  • كل عناصر الاختيار تعمل بنمط واحد: value للعرض و onChanged مع setState للتحديث.
  • Checkbox داخل النماذج، و Switch للإعدادات الفورية.
  • RadioGroup لاختيار واحد من قليل، و DropdownMenu لاختيار واحد من كثير.
  • Slider لرقم من مدى، مع divisions و label.
  • showDatePicker دالة async تُرجع DateTime?، فافحص null.