ما هو 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لتجنب مشاكل التمرير.