ما هو SwitchListTile؟
SwitchListTile هو نسخة مريحة من ListTile تحتوي على مفتاح تبديل (Switch) مدمج. تُستخدم عادة لعرض خيار قابل للتشغيل/الإيقاف داخل قائمة (مثل صفحة الإعدادات). يسمح لك بعرض title, subtitle, وsecondary جنبًا إلى جنب مع الـ Switch، والصف كله قابل للنقر لتغيير الحالة (إذا وُفِّر onChanged).
البنية الأساسية (constructor)
SwitchListTile({
Key? key,
required bool value,
required ValueChanged<bool>? onChanged,
Widget? title,
Widget? subtitle,
Widget? secondary,
bool isThreeLine = false,
bool dense,
bool selected = false,
Color? activeColor,
Color? activeTrackColor,
Color? inactiveThumbColor,
Color? inactiveTrackColor,
ImageProvider? activeThumbImage,
ImageProvider? inactiveThumbImage,
MouseCursor? mouseCursor,
bool autofocus = false,
Color? focusColor,
Color? hoverColor,
MaterialTapTargetSize? materialTapTargetSize,
ListTileControlAffinity controlAffinity = ListTileControlAffinity.trailing,
EdgeInsetsGeometry? contentPadding,
})Dartيوجد أيضاً SwitchListTile.adaptive(...) — يتكيّف شكله مع النظام (مثلاً iOS غالبًا يعطي مظهر مختلف).
أهم الخصائص وشرحها سريعًا
value(bool) — الحالة الحالية للمفتاح.onChanged(ValueChanged<bool>?) — تُستدعى عند تغيّر الحالة؛ إذا كانتnullيصبح العنصر معطلاً.title/subtitle— نصوص العرض الأساسية.secondary— ويدجت ثانوية (غالبًاIconأوCircleAvatar) تظهر في طرف العنصر الآخر.controlAffinity— يحدد مكان المفتاح:ListTileControlAffinity.trailing(افتراضي) → المفتاح على الطرف النهائي (يمين في RTL، يسار في LTR حسب الاتجاه).ListTileControlAffinity.leading→ المفتاح قبل المحتوى.ListTileControlAffinity.platform→ سلوك حسب النظام.
activeColor— لون الـ thumb عند التفعيل (يفضل استخدام SwitchTheme للتخصيص الشامل).activeTrackColor— لون مسار المفتاح عند التفعيل.inactiveThumbColor/inactiveTrackColor— الألوان عند التعطيل.activeThumbImage/inactiveThumbImage— صورة توضع فوق الـ thumb.isThreeLine— يسمح بثلاثة أسطر (title + subtitle يمكن أن يلتف لأسطر متعددة).dense— يقلل المسافات داخل الصف.selected— يغيّر نمط النص/أيقونة ليظهر كمحدد.contentPadding— التحكم بهوامش الداخل.autofocus,mouseCursor,focusColor,hoverColor,materialTapTargetSize— لميزات الوصول والسطح/ويب.
سلوك مهم
- النقر على أي مكان في الصف (ليس فقط على الزر) يغيّر الحالة — طالما
onChangedليسnull. هذا يجعل التجربة بديهية للمستخدم. - إذا أردت أن يكون فقط الـ switch هو القابل للنقر (وليس الصف كله)، عندها يجب بناء
ListTileمخصّص ووضَعSwitchفيtrailingمع منع التبديل عبر نقر الصف.
أمثلة عملية
1 — أبسط استخدام
bool notifications = false;
SwitchListTile(
title: Text('الإشعارات'),
subtitle: Text('تشغيل/إيقاف الإشعارات'),
value: notifications,
onChanged: (val) => setState(() => notifications = val),
);Dart2 — مع أيقونة ثانوية (secondary) والمفتاح على اليسار
SwitchListTile(
title: Text('الوضع الليلي'),
secondary: Icon(Icons.brightness_2),
value: isDark,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (v) => setState(() => isDark = v),
);Dart3 — تعطيل العنصر
SwitchListTile(
title: Text('مزامنة الخلفية'),
value: false,
onChanged: null, // معطّل
);Dart4 — نسخة متكيّفة (تغيّر الشكل عند iOS)
SwitchListTile.adaptive(
title: Text('مزامنة الصور'),
value: syncPhotos,
onChanged: (v) => setState(() => syncPhotos = v),
);Dart5 — صورة على الـ thumb
SwitchListTile(
title: Text('خاصية مع صورة'),
value: myVal,
activeThumbImage: AssetImage('assets/on_icon.png'),
inactiveThumbImage: AssetImage('assets/off_icon.png'),
onChanged: (v) => setState(() => myVal = v),
);Dart6 — التبديل غير متزامن (نمط optimistic + revert on failure)
SwitchListTile(
title: Text('ميزة سحابة'),
value: enabled,
onChanged: (v) async {
// تحديث واجهة المستخدم مبدئيًا
setState(() => enabled = v);
try {
await myApi.updateFeatureEnabled(v); // نداء الشبكة
} catch (e) {
// إذا فشل، ارجع للحالة القديمة وأخبر المستخدم
setState(() => enabled = !v);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('فشل التحديث')));
}
},
);Dartالتيمينغ (تخصيص مظهر مركزي)
- لتخصيص ألوان كل الـ Switch في التطبيق:
MaterialApp(
theme: ThemeData(
switchTheme: SwitchThemeData(
thumbColor: MaterialStateProperty.resolveWith((states) {
if (states.contains(MaterialState.selected)) return Colors.white;
return Colors.grey.shade200;
}),
trackColor: MaterialStateProperty.resolveWith((states) {
if (states.contains(MaterialState.selected)) return Colors.blue;
return Colors.grey;
}),
),
listTileTheme: ListTileThemeData(
iconColor: Colors.blueGrey,
textColor: Colors.black87,
),
),
home: ...
)DartSwitchListTile يرث مظهر الـ Switch من switchTheme، ونمط النص/أيقونة من listTileTheme.
التكامل مع نماذج الحالة (State management)
- Provider / Riverpod / Bloc: بدّل
valueوonChangedلقراءة/تحديث الحالة من الـ provider أو bloc بدلاً منsetState.
مثال بسيط مع Provider:
class SettingsModel with ChangeNotifier {
bool _notifications = true;
bool get notifications => _notifications;
set notifications(bool v) { _notifications = v; notifyListeners(); }
}
// في الواجهة
Consumer<SettingsModel>(
builder: (_, model, __) => SwitchListTile(
title: Text('الإشعارات'),
value: model.notifications,
onChanged: (v) => model.notifications = v,
),
);Dartدمجه داخل Form
لا يوجد SwitchListTileFormField مدمج، لكن يمكنك لفه بـ FormField<bool> لتوفر validation وsave:
FormField<bool>(
initialValue: initialValue,
builder: (state) {
return SwitchListTile(
title: Text('مشاركة الموقع'),
value: state.value ?? false,
onChanged: (v) {
state.didChange(v);
},
);
},
validator: (val) => val == true ? null : 'يرجى تفعيل المشاركة',
);Dartنصائح وملاحظات عملية
- التفاعل الكلي للصف: قابلية النقر في كامل الصف مفيدة في شاشات الإعداد — لكن إذا كنت تريد زر إعدادات ثانوي قابل للنقر (مثل زر المعلومات) تعامل مع ذلك بشكل منفصل.
- استخدم
.adaptiveللمطابقة مع تصاميم النظام (iOS vs Android) بسهولة. - الأداء: في قوائم طويلة استخدم
ListView.builderوconstحيث أمكن. إن كان لديك كثير منSwitchListTile، حاول أن تجعل كل عنصرconst/خالي من الحسابات الثقيلة أو استخدم keys مناسبة لمنع إعادة بناء غير ضرورية. - تجنّب عمليات ثقيلة في
onChangedمباشرة — إذا كانت تستدعي الشبكة، استعمل نمط optimistic أو أظهر مؤشر تحميل واضح ولا تحظر الواجهة. قد ترد بعمل disable مؤقت للعنصر أثناء العملية. - الوصول (Accessibility):
titleوsubtitleيُقرأان للقارئ الشاشة. إذا تحتاج وصفاً خاصاً أكثر استخدمSemanticsحول الـSwitchListTileلتزويدlabelأوhint. - التحكم في موقع المفتاح عبر
controlAffinityإذا أردت المفتاح في اليسار (مثلاً في قائمة RTL خاصة أو لتصميم معين). - التخصيص الشامل: لتغيير سلوك التينت في Material 3، انتبه إلى
surfaceTintColorوswitchThemeلأن بعض القيم قد تُطبّق افتراضيًا من الثيم.
أخطاء شائعة وحلولها
- خطأ: لا يتغير الـ Switch عند النقر
- السبب:
onChanged=nullأوvalueلا يُحدّث (نسيتsetStateأو لم تحدث القيمة في الـ provider). - الحل: تأكد أن
onChangedيعطي قيمة وأنك تُحدِّث الحالة فعليًا.
- السبب:
- أريد أن أسمح فقط بالنقر على الـ thumb وليس على الصف
- الحل: لا تستخدم
SwitchListTileمباشرة؛ بدلاً من ذلك اصنعListTileعادي وضعSwitchفيtrailingمع ضبطonTapالخاص بالـListTileإذا أردت سلوك مخصص.
- الحل: لا تستخدم
- التباعد/القياس غير المناسب في القوائم
- الحل: استخدم
dense,contentPadding, وisThreeLineللتحكم بالارتفاع والمسافات.
- الحل: استخدم