ما هو Radio
؟
Radio
هو ويدجت لزر اختيار دائري يُستخدم لاختيار قيمة واحدة من مجموعة قيم متنافسة (mutually exclusive). كل مجموعة راديو تملك groupValue
مشترك و كل زر له value
خاص؛ الذي يساوي groupValue
يظهر كـ «محدد».
الفكرة الأساسية (قاعدة العمل)
- لكل زر:
value
(قيمة هذا الزر). - للمجموعة:
groupValue
(القيمة المختارة حالياً). - عند النقر، يُستدعى
onChanged
وتمرر القيمة الجديدة — عليك تحديثgroupValue
لعرض التغيير.
إذا onChanged == null
→ الزر معطّل (disabled).
البنية الأساسية (مثال بسيط)
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String? favoriteColor; // القيمة المختارة
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("اختيار اللون المفضل")),
body: Column(
children: [
RadioListTile<String>(
title: Text("أحمر"),
value: "red",
groupValue: favoriteColor,
onChanged: (value) {
setState(() {
favoriteColor = value;
});
},
),
RadioListTile<String>(
title: Text("أخضر"),
value: "green",
groupValue: favoriteColor,
onChanged: (value) {
setState(() {
favoriteColor = value;
});
},
),
RadioListTile<String>(
title: Text("أزرق"),
value: "blue",
groupValue: favoriteColor,
onChanged: (value) {
setState(() {
favoriteColor = value;
});
},
),
SizedBox(height: 20),
Text(
favoriteColor == null
? "لم تختر أي لون بعد"
: "لونك المفضل هو: $favoriteColor",
style: TextStyle(fontSize: 18),
),
],
),
),
);
}
}
Dart// تعريف enum (موصى به بدل أعداد أو نصوص)
enum ColorOption { red, green, blue }
class MyRadioDemo extends StatefulWidget {
@override
State<MyRadioDemo> createState() => _MyRadioDemoState();
}
class _MyRadioDemoState extends State<MyRadioDemo> {
ColorOption? _selected; // nullable مفيد إذا سمحت بإلغاء الاختيار (toggleable)
@override
Widget build(BuildContext context) {
return Column(
children: [
Radio<ColorOption>(
value: ColorOption.red,
groupValue: _selected,
onChanged: (ColorOption? v) => setState(() => _selected = v),
),
Radio<ColorOption>(
value: ColorOption.green,
groupValue: _selected,
onChanged: (v) => setState(() => _selected = v),
),
Radio<ColorOption>(
value: ColorOption.blue,
groupValue: _selected,
onChanged: (v) => setState(() => _selected = v),
),
],
);
}
}
Dartأهم الخصائص وشرحها
value
(T) — قيمة هذا الراديو.groupValue
(T? ) — القيمة الحالية للمجموعة؛ إذاvalue == groupValue
يظهر كمحدد.onChanged
(ValueChanged<T?>?) — دالة تُستدعى عند النقر. إذاnull
يصبح الزر معطّل.toggleable
(bool) — إذاtrue
يمكن إلغاء الاختيار بالنقر على الزر المختار (فيتغيرonChanged
ويمرر null)؛ اجعل state متغيرًا nullable لتدعم هذا.fillColor
(MaterialStateProperty<Color?>?) — لون النقطة الداخلية (يُفضّل استخدامMaterialStateProperty.resolveWith
لتغيير اللون حسب الحالة مثل selected/disabled).activeColor
(Color?) — طريقة قديمة لتلوين؛ الآن يفضلfillColor
.splashRadius
(double?) — نصف قطر التأثير البصري عند النقر.mouseCursor
,focusNode
,autofocus
,materialTapTargetSize
,visualDensity
,focusColor
,hoverColor
— خصائص تحكم التفاعل والولوجية وسلوك الماوس/لوحة المفاتيح.- ملاحظة: أسماء بعض الخصائص قد تتغير بإصدارات فلاتر المختلفة — لكن الفكرة العامة نفسها.
استخدام عملي متقدم — RadioListTile
للحصول على صف كامل قابل للنقر مع عنوان ووصف وأيقونة استخدم RadioListTile<T>
:
RadioListTile<ColorOption>(
title: Text('أحمر'),
subtitle: Text('وصف أحمر'),
secondary: Icon(Icons.stop),
value: ColorOption.red,
groupValue: _selected,
onChanged: (v) => setState(() => _selected = v),
);
Dartميزة RadioListTile
: عند النقر في أي مكان في الصف يتغير الاختيار (مساحة نقر أكبر وسهولة استخدام).
تعطيل/تمكين عناصر
- لتعطيل زر مفرد: اجعل
onChanged: null
. - لتعطيل المجموعة كلها: امسك الحالة في مكان مركزي (مثلاً provider) ومرِّر
onChanged: null
أو تجاهل التحديثات.
تكامل مع النماذج (Form) و Validation
لا يوجد RadioFormField
جاهز، لكن يمكنك استخدام FormField<T>
:
FormField<ColorOption>(
initialValue: ColorOption.red,
validator: (val) => val == null ? 'اختر خيارًا' : null,
builder: (state) => Column(
children: [
RadioListTile<ColorOption>(
value: ColorOption.red,
groupValue: state.value,
onChanged: (v) => state.didChange(v),
title: Text('أحمر'),
),
if (state.hasError) Text(state.errorText!, style: TextStyle(color: Colors.red)),
],
),
)
Dartالتخصيص عبر Theme
لتوحيد مظهر كل الـ Radios في التطبيق:
MaterialApp(
theme: ThemeData(
radioTheme: RadioThemeData(
fillColor: MaterialStateProperty.resolveWith<Color?>((states) {
if (states.contains(MaterialState.disabled)) return Colors.grey;
if (states.contains(MaterialState.selected)) return Colors.blue;
return Colors.grey.shade400;
}),
splashRadius: 20,
),
),
home: MyHome(),
)
Dartاستخدم MaterialStateProperty
لتحديد ألوان مختلفة للحالات: selected
, disabled
, hovered
, pressed
, إلخ.
الوصول (Accessibility) و Keyboard
- استخدم
RadioListTile
أو غلّفRadio
بـSemantics
لتزويد تسميات (labels) و hints لقراء الشاشة. - عناصر الراديو تدعم التنقل عبر لوحة المفاتيح (arrow keys) عند التركيز ضمن مجموعة، ويمكن استخدام
focusNode
وautofocus
لضبط سلوك التركيز.
مقارنة سريعة مع Checkbox و Switch
- Radio: اختيار واحد من مجموعة (mutually exclusive).
- Checkbox: خيارات متعددة ممكن تحديدها أو إزالتها (multi-select).
- Switch: تبديل حالة ثنائية (on/off) لخاصية واحدة وليس اختيارًا من مجموعة.
نصائح عملية وأخطاء شائعة
- استخدم enum لقيم
value
لتكون واضحة ومحمية من الأخطاء بدلاً من أعداد أو سلاسل نصية. - اجعل حالة المجموعة nullable (
T?
) إذا استخدمتtoggleable: true
(حتى تسمح بإلغاء الاختيار). - لا تنس تحديث الـ state داخل
onChanged
— إذا لم تحدثه لن يرى المستخدم أي تغيير. - لبيئة إعدادات استخدم
RadioListTile
لسهولة الاستخدام ومساحة النقر الكبيرة. - لأداء قوائم طويلة استخدم
ListView.builder
ولا تعيد بناء كامل الواجهة عند اختيار عنصر—فكّر في فصل كل صف إلى ويدجت مستقل باستخدامconst
وValueKey
عند الحاجة. - عند الحاجة لتدرّج أو خلفيات مخصصة غلّف الـ Radio بحاوية أو استخدم
RadioListTile
داخل Card معshape
وclipBehavior
.
مثال متكامل (enum + toggleable + RadioListTile)
enum Gender { male, female, other }
class GenderSelector extends StatefulWidget {
@override
State<GenderSelector> createState() => _GenderSelectorState();
}
class _GenderSelectorState extends State<GenderSelector> {
Gender? _gender; // nullable لدعم toggleable
@override
Widget build(BuildContext context) {
return Column(
children: [
RadioListTile<Gender>(
title: Text('ذكر'),
value: Gender.male,
groupValue: _gender,
onChanged: (g) => setState(() => _gender = g),
),
RadioListTile<Gender>(
title: Text('أنثى'),
value: Gender.female,
groupValue: _gender,
onChanged: (g) => setState(() => _gender = g),
),
RadioListTile<Gender>(
title: Text('آخر'),
value: Gender.other,
groupValue: _gender,
onChanged: (g) => setState(() => _gender = g),
),
// مثال على toggleable:
Radio<Gender>(
value: Gender.male,
groupValue: _gender,
toggleable: true,
onChanged: (g) => setState(() => _gender = g),
),
],
);
}
}
Dartخلاصة سريعة
- استخدم
Radio
لاختيار قيمة واحدة من مجموعة. - إدارة الحالة عبر
groupValue
وonChanged
. - استخدم
RadioListTile
لواجهة مستخدم أفضل ومساحة نقر أكبر. - خصّص الألوان عبر
radioTheme
أوfillColor
وMaterialStateProperty
. - احرص على الوصول (Semantics) وتحديث الحالة بشكل صحيح.
📌 ما هو RadioListTile؟
هو ويدجت جاهزة في Flutter توفر:
- زر Radio (زر اختيار دائري).
- عنوان (
title
). - وصف (
subtitle
) اختياري. - أيقونة جانبية (
secondary
) اختيارية. - إمكانية النقر على أي مكان في الصف لتغيير الاختيار، مش بس على الدائرة.
يعني هو مخصص لعمل قوائم خيارات (مثل إعدادات اختيار اللغة أو الجنس) بطريقة منظمة وأنيقة.
🔹 البنية الأساسية
RadioListTile<T>(
value: ... , // قيمة هذا العنصر
groupValue: ... , // القيمة المحددة حاليًا للمجموعة
onChanged: ... , // ماذا يحدث عند التغيير
title: ... , // العنوان
subtitle: ... , // وصف فرعي (اختياري)
secondary: ... , // أيقونة أو ويدجت جانبية (اختياري)
)
Dart⚙️ أهم الخصائص
الخاصية | النوع | الوصف |
---|---|---|
value | T | القيمة التي يمثلها هذا الخيار |
groupValue | T? | القيمة الحالية للمجموعة (القيمة المختارة) |
onChanged | ValueChanged<T?>? | الدالة التي تنفذ عند النقر أو التغيير |
title | Widget | عنوان الخيار |
subtitle | Widget? | نص فرعي (مثلاً وصف) |
secondary | Widget? | أيقونة أو صورة على الجهة الأخرى |
toggleable | bool | إذا كان يمكن إلغاء التحديد (مثل زر On/Off) |
activeColor | Color? | لون دائرة الراديو عند التحديد (قد يُستبدل بـ fillColor ) |
fillColor | MaterialStateProperty<Color?>? | طريقة حديثة لتحديد ألوان الراديو بناءً على الحالة |
controlAffinity | ListTileControlAffinity | موقع دائرة الراديو (leading أو trailing أو platform default) |
contentPadding | EdgeInsetsGeometry? | المسافة الداخلية حول المحتوى |
selected | bool | هل يظهر الصف كمحدد بصريًا (Bold أو لون مختلف) |
selectedTileColor | Color? | لون الخلفية عند التحديد |
tileColor | Color? | لون الخلفية العادي |
dense | bool? | لتقليل حجم المسافات (شكل مضغوط) |
visualDensity | VisualDensity? | تحكم في كثافة العرض |
shape | ShapeBorder? | شكل الحواف (مربعة، مستديرة…) |
isThreeLine | bool | إذا كان لديك عنوان وسطرين من النصوص |
🖼 مثال بسيط
enum Gender { male, female }
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Gender? _selectedGender;
@override
Widget build(BuildContext context) {
return Column(
children: [
RadioListTile<Gender>(
title: Text("ذكر"),
value: Gender.male,
groupValue: _selectedGender,
onChanged: (Gender? value) {
setState(() {
_selectedGender = value;
});
},
),
RadioListTile<Gender>(
title: Text("أنثى"),
value: Gender.female,
groupValue: _selectedGender,
onChanged: (value) {
setState(() {
_selectedGender = value;
});
},
),
],
);
}
}
Dart🎨 تخصيص الألوان والمظهر
RadioListTile<String>(
title: Text("الخيار الأول"),
subtitle: Text("وصف قصير"),
secondary: Icon(Icons.star),
value: "option1",
groupValue: selectedOption,
onChanged: (value) => setState(() => selectedOption = value),
activeColor: Colors.pink, // لون الدائرة عند التحديد
tileColor: Colors.grey.shade100, // لون الخلفية
selectedTileColor: Colors.pink.shade50, // خلفية عند التحديد
controlAffinity: ListTileControlAffinity.trailing, // ضع الدائرة على اليمين
)
Dart📱 استخداماته الشائعة
- اختيار نوع من عدة خيارات (الجنس، اللغة، الوضع الليلي…).
- عمل قوائم إعدادات تفاعلية.
- استبدال
Radio
+ListTile
بكود أقصر وأكثر ترتيبًا.
📝 نصائح
- استخدم enum بدل النصوص لتجنب الأخطاء في القيم.
- إذا كنت تريد أن يتمكن المستخدم من إلغاء التحديد، استخدم
toggleable: true
واجعلgroupValue
قابل لأن يكونnull
. - إذا لديك قائمة طويلة من الخيارات، ضعها داخل
ListView
لتجنب مشاكل التمرير.