شارك المقالة

تنظيم الثيمين الفاتح والداكن في Flutter داخل مصدر مشترك، مع ThemeMode.system وlight وdark وتصحيح الخصائص القديمة وفصل اختيار الثيم عن حفظه.

التعريف والاختيار مسؤوليتان

theme يحدد تصميم الوضع الفاتح، وdarkTheme يحدد تصميم الوضع الداكن، بينما themeMode يقرر أيهما يستخدم. وجود darkTheme وحده لا يعني أن التطبيق سيظل داكنًا، كما أن وضع ThemeMode.dark ثابتًا لا يسمح لزر داخل الصفحة بتغييره من تلقاء نفسه. حدد مصدر الاختيار في State أعلى MaterialApp أو مالك حالة مناسب. المثال يملك ثلاثة اختيارات واضحة ويعرض الثيم الفعلي حتى يمكن التمييز بين الوضع المختار ونتيجة اتباع النظام.

نظم الملف دون نسخ المنطق

AppThemes في المثال يجمع تعريفين ثابتين مبنيين من دالة واحدة تستقبل Brightness. يمكنك نقل هذا الصنف إلى lib/theme/app_themes.dart واستيراده من نقطة التطبيق بدل تكرار الألوان في كل شاشة. الملف الواحد هنا لتسهيل التجربة، وليس شرطًا لبنية كل مشروع. لا تنقل حالة المستخدم والقراءة من التخزين إلى ملف ثوابت الثيم؛ فصل بيانات التصميم عن قرار الاختيار وعن الخدمة التي تحفظه يجعل اختبار كل جزء أسهل.

ثلاثة أوضاع بدل bool غامض

ThemeMode.system يتبع سطوع النظام، وlight يفرض الفاتح، وdark يفرض الداكن. تحويل القرار إلى bool باسم isDark قد يفقد اختيار النظام إذا كنت تحتاجه لاحقًا. كذلك الثيم الداكن الفعلي لا يخبرك وحده هل اختاره المستخدم أم جاء من النظام؛ لذلك لا تحفظ Brightness الحالي بدل اختيار ThemeMode. المثال لا يكتب إلى shared_preferences، فإذا أغلقت التطبيق يعود الاختيار الابتدائي system؛ الحفظ الدائم موضح في المقال المرتبط مع خدمة مستقلة.

الثيم الداكن ليس قلب ألوان

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

تحديث أمثلة قديمة

backwardsCompatibility في AppBarTheme وأسماء bodyText1 وbodyText2 في أمثلة قديمة ليست وصفة حديثة تنسخ دون مراجعة. استخدم API المتوافق مع الإصدار الذي يعمل عليه مشروعك، وأبقِ overrides بأقل قدر يخدم التصميم. HexColor ليس ضرورة، لأن Color يدعم القيم المطلوبة مباشرة. لا تضف SystemChrome داخل build لتغيير شريط النظام مع كل إعادة بناء؛ راجع طبقة AppBar وسلوك edge-to-edge والخصائص المدعومة على المنصة إذا كان هذا جزءًا من المتطلبات.

كيف تختبر النظام والاختيار؟

اختر Dark ثم غيّر سطوع النظام في الاختبار؛ يجب أن يظل الاختيار الصريح داكنًا. اختر System وغيّر سطوع المنصة؛ يجب أن يتبعه التطبيق. راجع أن الفقرة والبطاقة والأيقونات تتغير ولا تبقى نصوص مخفية بسبب لون ثابت، ثم جرّب حجم خط كبيرًا وعرضًا ضيقًا. المثال لا يقيس كل مكوّن في تطبيقك ولا يتحكم في إعداد نظام التشغيل نفسه، بل يغير ThemeMode داخل التطبيق. قبل نقل AppThemes راجع الشاشات التي تستعمل أنماطًا محلية قد تتغلب عليه.

مثال مستقل للتجربة

شغّل المثال داخل مشروع Flutter تجريبي. يعتمد على Material فقط ولا يحتاج assets أو أصنافًا من مشروع خاص. غيّر مساحة النافذة وجرّب تكبير النص أثناء الفحص، ثم انقل المكوّن إلى مشروعك بعد تحديد سياق قيوده الفعلي.

import 'package:flutter/material.dart';
void main() => runApp(const DemoApp());
abstract final class AppThemes {
  static final light = _build(Brightness.light);
  static final dark = _build(Brightness.dark);
  static ThemeData _build(Brightness brightness) => ThemeData(
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF00695C), brightness: brightness),
    inputDecorationTheme: const InputDecorationTheme(border: OutlineInputBorder()),
  );
}
class DemoApp extends StatefulWidget {
  const DemoApp({super.key});
  @override
  State<DemoApp> createState() => _DemoAppState();
}
class _DemoAppState extends State<DemoApp> {
  ThemeMode _mode = ThemeMode.system;
  @override
  Widget build(BuildContext context) => MaterialApp(
    theme: AppThemes.light, darkTheme: AppThemes.dark, themeMode: _mode,
    home: Builder(builder: (pageContext) => Scaffold(
      appBar: AppBar(title: const Text('Theme modes')),
      body: ListView(padding: const EdgeInsets.all(16), children: [
        Text('Selected: ${_mode.name}'),
        Text('Effective: ${Theme.of(pageContext).brightness.name}'),
        const SizedBox(height: 16),
        Wrap(spacing: 8, runSpacing: 8, children: [
          for (final mode in ThemeMode.values)
            OutlinedButton(onPressed: () => setState(() { _mode = mode; }), child: Text(mode.name)),
        ]),
        const SizedBox(height: 16),
        const Card(child: Padding(padding: EdgeInsets.all(16), child: Text('A shared component follows the selected theme.'))),
      ]),
    )),
  );
}

مرجع الواجهة البرمجية

راجع التوثيق الرسمي لمطابقة السلوك مع إصدار Flutter المستخدم. نجاح المثال في مساحة واحدة لا يثبت توافق التصميم مع جميع الشاشات؛ اختبر حدود الانتقال والاتجاه واللغة والنص الطويل.

الصور والفيديو من الشرح السابق

الكود النصي أعلاه هو النسخة المصححة؛ قد تختلف المواد المرئية السابقة في أسماء المتغيرات أو طريقة التشغيل.

إنشاء ملف style للtheme كود استدعاء theme في main class
شاهد أيضًا
مقالات ذات صلة
🚫 مانع الإعلانات مفعل

يجب إيقاف مانع الإعلانات لاستكمال تصفح الموقع