أساسيات ودجات Flutter

ودجات Cupertino (نمط iOS)

45 دقيقة الدرس 13 من 18

ودجات نمط iOS في Flutter

يتضمن Flutter مجموعة كاملة من ودجات نمط iOS تحت مكتبة cupertino. هذه الودجات تحاكي المظهر والإحساس الأصلي لتطبيقات iOS مما يجعل تطبيق Flutter يبدو مألوفاً على أجهزة Apple. يمكنك حتى مزج ودجات Material و Cupertino في نفس التطبيق لإنشاء واجهات متكيفة مع المنصة.

ملاحظة: لاستخدام ودجات Cupertino استورد package:flutter/cupertino.dart. يمكنك استيراد كلا material.dart و cupertino.dart في نفس الملف دون تعارضات.

CupertinoApp و CupertinoPageScaffold

CupertinoApp هو مكافئ iOS لـ MaterialApp. يُعد سمات Cupertino. CupertinoPageScaffold يوفر هيكل الصفحة الأساسي مع شريط تنقل ومنطقة محتوى.

تطبيق Cupertino أساسي

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyCupertinoApp());

class MyCupertinoApp extends StatelessWidget {
  const MyCupertinoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      title: 'تطبيق نمط iOS',
      theme: CupertinoThemeData(
        primaryColor: CupertinoColors.activeBlue,
        brightness: Brightness.light,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return const CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text('الرئيسية'),
      ),
      child: Center(
        child: Text('مرحباً iOS!'),
      ),
    );
  }
}

CupertinoNavigationBar

شريط التنقل بنمط iOS يقع في الأعلى مع عنوان في الوسط وودجات اختيارية في البداية والنهاية.

شريط تنقل مع إجراءات

CupertinoPageScaffold(
  navigationBar: CupertinoNavigationBar(
    middle: const Text('الإعدادات'),
    leading: CupertinoButton(
      padding: EdgeInsets.zero,
      onPressed: () => Navigator.pop(context),
      child: const Icon(CupertinoIcons.back),
    ),
    trailing: CupertinoButton(
      padding: EdgeInsets.zero,
      onPressed: () {
        debugPrint('تم الضغط على تعديل');
      },
      child: const Text('تعديل'),
    ),
  ),
  child: const SafeArea(
    child: Center(child: Text('المحتوى هنا')),
  ),
)

CupertinoButton

زر بنمط iOS مع تأثير شفافية عند الضغط بدلاً من تموج Material.

أزرار Cupertino

Column(
  children: [
    // زر افتراضي (نصي)
    CupertinoButton(
      onPressed: () => debugPrint('تم الضغط'),
      child: const Text('زر نصي'),
    ),

    // زر مملوء
    CupertinoButton.filled(
      onPressed: () => debugPrint('تم الضغط على المملوء'),
      child: const Text('زر مملوء'),
    ),

    // زر معطّل
    const CupertinoButton(
      onPressed: null,
      child: Text('معطّل'),
    ),

    // زر بتنسيق مخصص
    CupertinoButton(
      color: CupertinoColors.destructiveRed,
      borderRadius: BorderRadius.circular(8),
      onPressed: () => debugPrint('حذف'),
      child: const Text('حذف الحساب'),
    ),
  ],
)

CupertinoTextField

حقل نص بنمط iOS مع حدود مستديرة ونص عنصر نائب.

أمثلة CupertinoTextField

Column(
  children: [
    // حقل نص أساسي
    const CupertinoTextField(
      placeholder: 'أدخل اسمك',
      padding: EdgeInsets.all(12),
    ),

    const SizedBox(height: 16),

    // حقل نص منسق مع بادئة
    CupertinoTextField(
      placeholder: 'بحث...',
      prefix: const Padding(
        padding: EdgeInsets.only(left: 8),
        child: Icon(
          CupertinoIcons.search,
          color: CupertinoColors.systemGrey,
        ),
      ),
      decoration: BoxDecoration(
        color: CupertinoColors.systemGrey6,
        borderRadius: BorderRadius.circular(10),
      ),
      padding: const EdgeInsets.all(12),
      clearButtonMode: OverlayVisibilityMode.editing,
    ),

    const SizedBox(height: 16),

    // حقل كلمة المرور
    const CupertinoTextField(
      placeholder: 'كلمة المرور',
      obscureText: true,
      padding: EdgeInsets.all(12),
      suffix: Padding(
        padding: EdgeInsets.only(right: 8),
        child: Icon(
          CupertinoIcons.eye_slash,
          color: CupertinoColors.systemGrey,
        ),
      ),
    ),
  ],
)

CupertinoSwitch و CupertinoSlider

مفاتيح التبديل ومنزلقات بنمط iOS.

المفتاح والمنزلق

class ControlsExample extends StatefulWidget {
  const ControlsExample({super.key});

  @override
  State<ControlsExample> createState() => _ControlsExampleState();
}

class _ControlsExampleState extends State<ControlsExample> {
  bool _wifiEnabled = true;
  double _brightness = 0.7;

  @override
  Widget build(BuildContext context) {
    return CupertinoListSection.insetGrouped(
      header: const Text('العرض والشبكة'),
      children: [
        CupertinoListTile(
          title: const Text('Wi-Fi'),
          trailing: CupertinoSwitch(
            value: _wifiEnabled,
            onChanged: (bool value) {
              setState(() => _wifiEnabled = value);
            },
          ),
        ),
        CupertinoListTile(
          title: const Text('السطوع'),
          trailing: SizedBox(
            width: 180,
            child: CupertinoSlider(
              value: _brightness,
              onChanged: (double value) {
                setState(() => _brightness = value);
              },
            ),
          ),
        ),
      ],
    );
  }
}
نصيحة: استخدم CupertinoListSection.insetGrouped لإنشاء تخطيط الإعدادات المجمعة الشائع في تطبيقات iOS. يتعامل تلقائياً مع الزوايا المستديرة ورؤوس الأقسام.

CupertinoAlertDialog

مربع حوار التنبيه بنمط iOS مع زوايا مستديرة وأزرار إجراءات مكدسة أو جنباً إلى جنب.

حوار تنبيه Cupertino

void _showCupertinoAlert(BuildContext context) {
  showCupertinoDialog(
    context: context,
    builder: (BuildContext ctx) {
      return CupertinoAlertDialog(
        title: const Text('حذف الصورة'),
        content: const Text(
          'سيتم حذف هذه الصورة من جميع أجهزتك. '
          'يمكنك استعادتها من المحذوفة مؤخراً لمدة 30 يوماً.',
        ),
        actions: [
          CupertinoDialogAction(
            onPressed: () => Navigator.pop(ctx),
            child: const Text('إلغاء'),
          ),
          CupertinoDialogAction(
            isDestructiveAction: true,
            onPressed: () {
              Navigator.pop(ctx);
              debugPrint('تم حذف الصورة');
            },
            child: const Text('حذف'),
          ),
        ],
      );
    },
  );
}

CupertinoActionSheet

ورقة الإجراءات تنزلق من الأسفل وتعرض مجموعة خيارات. تتضمن دائماً زر إلغاء.

ورقة إجراءات Cupertino

void _showActionSheet(BuildContext context) {
  showCupertinoModalPopup(
    context: context,
    builder: (BuildContext ctx) {
      return CupertinoActionSheet(
        title: const Text('مشاركة الصورة'),
        message: const Text('اختر كيف تريد مشاركة هذه الصورة.'),
        actions: [
          CupertinoActionSheetAction(
            onPressed: () {
              Navigator.pop(ctx);
              debugPrint('AirDrop');
            },
            child: const Text('AirDrop'),
          ),
          CupertinoActionSheetAction(
            onPressed: () {
              Navigator.pop(ctx);
              debugPrint('الرسائل');
            },
            child: const Text('الرسائل'),
          ),
          CupertinoActionSheetAction(
            isDestructiveAction: true,
            onPressed: () {
              Navigator.pop(ctx);
              debugPrint('حذف');
            },
            child: const Text('حذف الصورة'),
          ),
        ],
        cancelButton: CupertinoActionSheetAction(
          isDefaultAction: true,
          onPressed: () => Navigator.pop(ctx),
          child: const Text('إلغاء'),
        ),
      );
    },
  );
}

CupertinoPicker و CupertinoDatePicker

منتقيات عجلة التمرير بنمط iOS لاختيار القيم والتواريخ.

منتقي Cupertino

void _showPicker(BuildContext context) {
  final List<String> countries = [
    'السعودية', 'الإمارات', 'مصر',
    'الأردن', 'الكويت', 'قطر',
  ];

  showCupertinoModalPopup(
    context: context,
    builder: (ctx) {
      return Container(
        height: 250,
        color: CupertinoColors.systemBackground,
        child: CupertinoPicker(
          itemExtent: 36,
          onSelectedItemChanged: (int index) {
            debugPrint('تم الاختيار: \${countries[index]}');
          },
          children: countries.map((c) => Center(child: Text(c))).toList(),
        ),
      );
    },
  );
}

منتقي التاريخ Cupertino

void _showDatePicker(BuildContext context) {
  showCupertinoModalPopup(
    context: context,
    builder: (ctx) {
      return Container(
        height: 300,
        color: CupertinoColors.systemBackground,
        child: Column(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                CupertinoButton(
                  child: const Text('إلغاء'),
                  onPressed: () => Navigator.pop(ctx),
                ),
                CupertinoButton(
                  child: const Text('تم'),
                  onPressed: () => Navigator.pop(ctx),
                ),
              ],
            ),
            Expanded(
              child: CupertinoDatePicker(
                mode: CupertinoDatePickerMode.date,
                initialDateTime: DateTime.now(),
                minimumDate: DateTime(2000),
                maximumDate: DateTime(2030),
                onDateTimeChanged: (DateTime date) {
                  debugPrint('التاريخ: \$date');
                },
              ),
            ),
          ],
        ),
      );
    },
  );
}

ودجات متكيفة مع المنصة

يمكنك بناء ودجات تتبدل تلقائياً بين أنماط Material و Cupertino بناءً على المنصة.

حوار متكيف

import 'dart:io' show Platform;

void showAdaptiveAlert(BuildContext context) {
  if (Platform.isIOS || Platform.isMacOS) {
    showCupertinoDialog(
      context: context,
      builder: (ctx) => CupertinoAlertDialog(
        title: const Text('تنبيه'),
        content: const Text('حدث شيء ما.'),
        actions: [
          CupertinoDialogAction(
            onPressed: () => Navigator.pop(ctx),
            child: const Text('حسناً'),
          ),
        ],
      ),
    );
  } else {
    showDialog(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('تنبيه'),
        content: const Text('حدث شيء ما.'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(ctx),
            child: const Text('حسناً'),
          ),
        ],
      ),
    );
  }
}

مفتاح متكيف (مدمج)

// يوفر Flutter 3.x Switch.adaptive وغيرها
Switch.adaptive(
  value: _isEnabled,
  onChanged: (bool value) {
    setState(() => _isEnabled = value);
  },
)

// متاحة أيضاً:
// Slider.adaptive(...)
// CircularProgressIndicator.adaptive()
تحذير: مكتبة dart:io غير متاحة على Flutter Web. إذا كان تطبيقك يستهدف الويب استخدم Theme.of(context).platform أو defaultTargetPlatform من foundation.dart بدلاً من Platform.isIOS.

مثال عملي: نسخة إعدادات iOS

صفحة إعدادات iOS واقعية مبنية بالكامل بودجات Cupertino.

شاشة إعدادات iOS

class SettingsScreen extends StatefulWidget {
  const SettingsScreen({super.key});

  @override
  State<SettingsScreen> createState() => _SettingsScreenState();
}

class _SettingsScreenState extends State<SettingsScreen> {
  bool _airplaneMode = false;
  bool _wifi = true;
  bool _bluetooth = true;
  bool _notifications = true;

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(
        middle: Text('الإعدادات'),
      ),
      child: SafeArea(
        child: ListView(
          children: [
            CupertinoListSection.insetGrouped(
              children: [
                CupertinoListTile(
                  leading: Container(
                    padding: const EdgeInsets.all(4),
                    decoration: BoxDecoration(
                      color: CupertinoColors.systemOrange,
                      borderRadius: BorderRadius.circular(6),
                    ),
                    child: const Icon(
                      CupertinoIcons.airplane,
                      color: CupertinoColors.white,
                      size: 20,
                    ),
                  ),
                  title: const Text('وضع الطيران'),
                  trailing: CupertinoSwitch(
                    value: _airplaneMode,
                    onChanged: (v) => setState(() => _airplaneMode = v),
                  ),
                ),
                CupertinoListTile(
                  leading: Container(
                    padding: const EdgeInsets.all(4),
                    decoration: BoxDecoration(
                      color: CupertinoColors.activeBlue,
                      borderRadius: BorderRadius.circular(6),
                    ),
                    child: const Icon(
                      CupertinoIcons.wifi,
                      color: CupertinoColors.white,
                      size: 20,
                    ),
                  ),
                  title: const Text('Wi-Fi'),
                  additionalInfo: Text(_wifi ? 'الشبكة المنزلية' : 'معطّل'),
                  trailing: const CupertinoListTileChevron(),
                ),
                CupertinoListTile(
                  leading: Container(
                    padding: const EdgeInsets.all(4),
                    decoration: BoxDecoration(
                      color: CupertinoColors.activeBlue,
                      borderRadius: BorderRadius.circular(6),
                    ),
                    child: const Icon(
                      CupertinoIcons.bluetooth,
                      color: CupertinoColors.white,
                      size: 20,
                    ),
                  ),
                  title: const Text('البلوتوث'),
                  additionalInfo: Text(_bluetooth ? 'مفعّل' : 'معطّل'),
                  trailing: const CupertinoListTileChevron(),
                ),
              ],
            ),
            CupertinoListSection.insetGrouped(
              children: [
                CupertinoListTile(
                  leading: Container(
                    padding: const EdgeInsets.all(4),
                    decoration: BoxDecoration(
                      color: CupertinoColors.systemRed,
                      borderRadius: BorderRadius.circular(6),
                    ),
                    child: const Icon(
                      CupertinoIcons.bell_fill,
                      color: CupertinoColors.white,
                      size: 20,
                    ),
                  ),
                  title: const Text('الإشعارات'),
                  trailing: const CupertinoListTileChevron(),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

ملخص

  • CupertinoApp + CupertinoPageScaffold -- هيكل تطبيق iOS وتخطيط الصفحة
  • CupertinoNavigationBar -- شريط علوي بنمط iOS مع عنوان في الوسط
  • CupertinoButton -- زر ضغط بالشفافية (نصي ومملوء)
  • CupertinoTextField -- إدخال نص iOS مستدير مع عنصر نائب
  • CupertinoSwitch / CupertinoSlider -- عناصر تحكم تبديل ونطاق iOS
  • CupertinoAlertDialog -- تنبيه iOS مستدير مع إجراءات مكدسة
  • CupertinoActionSheet -- ورقة إجراءات سفلية مع زر إلغاء
  • CupertinoPicker / CupertinoDatePicker -- اختيار بعجلة التمرير
  • ودجات متكيفة مع المنصة: Switch.adaptive وفحوصات المنصة الشرطية

تمرين عملي

ابنِ شاشة إعدادات iOS كاملة باستخدام ودجات Cupertino فقط. أضف مفتاح وضع الطيران وصفوف Wi-Fi و Bluetooth مع أسهم التنقل وقسم الإشعارات وإجراء "تسجيل الخروج" الذي يعرض CupertinoActionSheet يطلب من المستخدم التأكيد. استخدم CupertinoListSection.insetGrouped للتخطيط المجمع.