Custom Fonts

1) ما هي الخطوط المخصصة ولماذا نستخدمها؟

الخطوط المخصصة هي ملفات خط (مثل .ttf, .otf) تدرجها داخل تطبيقك بدل الخط الافتراضي. نستخدمها للعلامة التجارية (branding)، تحسين القراءة، دعم لغات مخصصة أو استخدام خط محدد مطلوب للتصميم.

2) هيكل المجلد الموصى به

عادةً تضع ملفات الخطوط داخل مجلد assets/fonts/ في جذر المشروع:

my_flutter_app/
  assets/
    fonts/
      Inter-Regular.ttf
      Inter-Bold.ttf
      Cairo-Regular.ttf
      Cairo-Bold.ttf
  pubspec.yaml
  lib/
    main.dart
Dart
3) كيف تسجل الخطوط في pubspec.yaml

أهم نقطة: المسافات (indentation) مهمة جداً في YAML.

مثال: تسجيل عائلة خط واحد لها أوزان مختلفة:

flutter:
  uses-material-design: true

  assets:
    - assets/images/   # إن احتجت

  fonts:
    - family: Inter
      fonts:
        - asset: assets/fonts/Inter-Regular.ttf
        - asset: assets/fonts/Inter-Bold.ttf
          weight: 700
        - asset: assets/fonts/Inter-Italic.ttf
          style: italic
Dart

مثال لتسجيل عائلتي خط (Cairo للعربية و Inter للإنجليزية):

flutter:
  fonts:
    - family: Cairo
      fonts:
        - asset: assets/fonts/Cairo-Regular.ttf
        - asset: assets/fonts/Cairo-Bold.ttf
          weight: 700

    - family: Inter
      fonts:
        - asset: assets/fonts/Inter-Regular.ttf
        - asset: assets/fonts/Inter-Bold.ttf
          weight: 700
Dart

ملاحظات:

  • يمكنك اختيار اسم العائلة (family) أيًّا كان؛ هذا الاسم ستستخدمه لاحقًا في TextStyle(fontFamily: 'Inter').
  • weight هو قيمة عددية (100,200,…,900). استخدم 400 لـ normal، 700 لـ bold عادة.
  • style: italic يحدد النسخة المائلة.
4) استخدام الخطوط في كود Flutter (على مستوى ويدجت)
Text(
  'مرحبا بك!',
  style: TextStyle(
    fontFamily: 'Cairo', // نفس الاسم في pubspec.yaml
    fontSize: 20,
    fontWeight: FontWeight.w700, // يطابق weight: 700
  ),
)
Dart
5) تعيين الخط على مستوى التطبيق كله (Theme)

الأفضل عادةً تعيين الخط الافتراضي في ThemeData حتى لا تكرر fontFamily لكل Text.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Custom Fonts Demo',
      theme: ThemeData(
        fontFamily: 'Inter', // يستخدم لجميع الـ Text widgets إلا إن حُدّد خلافه
        textTheme: const TextTheme(
          headline1: TextStyle(fontFamily: 'Cairo', fontSize: 32, fontWeight: FontWeight.w700),
          bodyText1: TextStyle(fontSize: 16),
        ),
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('تجربة الخطوط')),
      body: const Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text('هذا نص بالعربية باستخدام Cairo', style: TextStyle(fontFamily: 'Cairo', fontSize: 20)),
            SizedBox(height: 12),
            Text('This is English text using Inter', style: TextStyle(fontSize: 18)),
          ],
        ),
      ),
    );
  }
}
Dart

هنا fontFamily: 'Inter' في ThemeData يجعل معظم النصوص تستخدم Inter افتراضيًا، لكننا غيّرنا بعض النصوص لتستخدم Cairo.

6) استخدام الأوزان وأنماط الخط (weight & style)
  • fontWeight في Flutter يقبل FontWeight.w100 .. FontWeight.w900.
  • يجب أن تكون ملفات الخط التي تضيفها تدعم الوزن المطلوب أو يظهر استبدال (browser fallback-like) — Flutter سيحاول استخدام أقرب وزن متوفر.
  • fontStyle: FontStyle.italic يستخدم ملفات style: italic إن مُسجّلة.

مثال:

Text(
  'Bold + italic',
  style: TextStyle(
    fontFamily: 'Inter',
    fontWeight: FontWeight.w700,
    fontStyle: FontStyle.italic,
  ),
)
Dart
7) دعم اللغات المتعددة (عربي + إنجليزي) وخيارات fallback

إذا أردت أن يكون الخط العربي منفصلًا عن الخط اللاتيني، يمكنك:

  • تسجيل خط عربي عائلة Cairo وخط إنجليزي Inter.
  • في TextStyle أو Theme, استخدم fontFamilyFallback (قائمة أسماء عائلات لحالة عدم توفر حرف في الخط الأول).
Text(
  'English and العربية',
  style: TextStyle(
    fontFamily: 'Inter',
    fontFamilyFallback: ['Cairo', 'Roboto'],
  ),
)
Dart

هنا إذا Inter لا تحتوي على حرف عربي مناسب، سيجرب Cairo ثم Roboto.

8) استخدام حزمة google_fonts (خيار سهل)

لو تفضل تحميل خطوط Google مباشرة عبر باكج، استخدم google_fonts (سهل الاستخدام، يدعم الكثير من الخطوط ويعطي واجهات جاهزة):

pubspec.yaml:

dependencies:
  google_fonts: ^5.0.0  # تأكد من نسخة حديثة عند الاستخدام
Dart

مثال:

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';

Text(
  'Hello with Google Fonts',
  style: GoogleFonts.inter(fontSize: 18, fontWeight: FontWeight.w600),
)
Dart

مزايا: سهل، يدعم تنزيل الخط محليًا عند البناء أو تحميله وقت التشغيل حسب الإعدادات. (ملحوظة: قد يتطلب اتصال إنترنت أثناء التطوير إذا لم تكن مخزّنًا محليًا).

9) تحميل خطوط في وقت التشغيل (advanced)

يمكنك تحميل خطوط ديناميكيًّا عبر FontLoader إذا احتجت ذلك (نادر الحاجة في معظم التطبيقات). هذا يتطلب العمل على مستوى byte data و rootBundle.load.

10) التحقق من المشكلات الشائعة وحلولها
  • الخط لا يظهر / يظهر الخط الافتراضي:
    • تحقق من المسار في pubspec.yaml (indentation والمسار بالضبط).
    • أعد تشغيل التطبيق (hot restart أو كامل restart). أحيانًا hot reload لا يكفي بعد تعديل pubspec.yaml.
  • اسم العائلة غير مطابق:
    • اسم العائلة (family) هو الذي تختاره في pubspec.yaml — استخدم نفس النص في fontFamily.
  • الوزن لا يتغير:
    • تأكد من أنك أدرجت ملفًا لذلك الوزن ووضعت weight: 700 في pubspec.yaml، واستخدم FontWeight.w700.
  • مشاكل مع العربية (ligatures أو kerning غير مرغوب):
    • بعض الخطوط العربية تحتاج إلى إعدادات OpenType؛ تأكد من اختيار خط عربي مناسب يدعم ligatures المطلوبة.
  • حجم التطبيق يكبر بسبب الخطوط:
    • ملفات الخط تضيف حجمًا. قلل عدد الأوزان أو استخدم خطوط نظام إن أمكن.
11) أمثلة عملية (ملف كامل جاهز)

pubspec.yaml (مقتطف):

flutter:
  uses-material-design: true

  fonts:
    - family: Inter
      fonts:
        - asset: assets/fonts/Inter-Regular.ttf
        - asset: assets/fonts/Inter-Medium.ttf
          weight: 500
        - asset: assets/fonts/Inter-Bold.ttf
          weight: 700

    - family: Cairo
      fonts:
        - asset: assets/fonts/Cairo-Regular.ttf
        - asset: assets/fonts/Cairo-Bold.ttf
          weight: 700
Dart

main.dart (مقتطف):

ThemeData(
  fontFamily: 'Inter',
  textTheme: TextTheme(
    headlineLarge: TextStyle(fontFamily: 'Cairo', fontSize: 30, fontWeight: FontWeight.w700),
    bodyMedium: TextStyle(fontSize: 16),
  ),
)
Dart
12) نصائح تصميمية وأفضل الممارسات
  • اختر عددًا مناسبًا من الأوزان فقط (مثلاً Regular, Medium, Bold) لتقليل حجم التطبيق.
  • ضع الخطوط في assets/fonts/ لتنظيم أفضل.
  • استخدم ThemeData لتوحيد المظهر بدلًا من ضبط fontFamily لكل Text.
  • استخدم fontFamilyFallback عند الحاجة لدعم لغات متعددة.
  • اختبر على أجهزة فعلية لأن طريقة التظهير قد تختلف بين المنصات (iOS vs Android).
13) Checklist سريع للتأكد من عمل الخط
  1. هل وضعت ملفات الخط في المسار الصحيح؟ assets/fonts/...
  2. هل أدرجتها في pubspec.yaml تحت fonts: بدقة indentation؟
  3. هل استعملت نفس اسم family في TextStyle(fontFamily: '...') أو في ThemeData؟
  4. هل قمت بعمل flutter clean ثم flutter pub get ثم restart إذا لم تظهر؟
  5. هل ملفات الخط تدعم الأوزان/الستايلات التي تستخدمها؟
14) أمثلة صغيرة لتجريب سريع
// 1: نص يستخدم الخط الافتراضي للتطبيق (Inter)
Text('Default app font (Inter)');

// 2: نص يفرض Cairo
Text('نص بالعربي - Cairo', style: TextStyle(fontFamily: 'Cairo'));

// 3: نص bold
Text('Bold text', style: TextStyle(fontWeight: FontWeight.w700));

// 4: fallback
Text(
  'English and العربية together',
  style: TextStyle(fontFamily: 'Inter', fontFamilyFallback: ['Cairo']),
);
Dart
🧱 الصيغة العامة لاستخدامه
ThemeData(
  fontFamily: 'Inter', // الخط الافتراضي العام
  textTheme: TextTheme(
    displayLarge: TextStyle(...),
    displayMedium: TextStyle(...),
    displaySmall: TextStyle(...),
    headlineLarge: TextStyle(...),
    headlineMedium: TextStyle(...),
    headlineSmall: TextStyle(...),
    titleLarge: TextStyle(...),
    titleMedium: TextStyle(...),
    titleSmall: TextStyle(...),
    bodyLarge: TextStyle(...),
    bodyMedium: TextStyle(...),
    bodySmall: TextStyle(...),
    labelLarge: TextStyle(...),
    labelMedium: TextStyle(...),
    labelSmall: TextStyle(...),
  ),
)
Dart
🧠 شرح كل نوع نص داخل TextTheme
الاسمالاستخدام الشائعالحجم الافتراضي التقريبي
displayLargeالعناوين الكبيرة جدًا (مثل شاشة ترحيب أو Splash)~57
displayMediumعنوان كبير ثانوي~45
displaySmallعنوان متوسط الحجم~36
headlineLargeعناوين رئيسية للشاشات أو الأقسام~32
headlineMediumعناوين داخل صفحات أو بطاقات~28
headlineSmallعنوان فرعي أو صغير نسبيًا~24
titleLargeعنوان عام لعنصر أو صفحة~22
titleMediumعناوين أقسام أو Widgets فرعية~16
titleSmallنصوص صغيرة مثل أزرار أو روابط~14
bodyLargeنصوص الفقرات الرئيسية~16
bodyMediumنصوص عادية متوسطة~14
bodySmallنصوص صغيرة جدًا~12
labelLargeالنصوص داخل الأزرار (مثل ElevatedButton)~14
labelMediumتسميات صغيرة~12
labelSmallتسميات دقيقة أو رموز صغيرة~11
🧾 مثال عملي شامل
import 'package:flutter/material.dart';

final ThemeData appTheme = ThemeData(
  fontFamily: 'Inter', // الخط الأساسي لجميع النصوص

  textTheme: const TextTheme(
    displayLarge: TextStyle(
      fontFamily: 'Cairo',
      fontSize: 48,
      fontWeight: FontWeight.bold,
      color: Colors.blueAccent,
    ),
    headlineLarge: TextStyle(
      fontFamily: 'Cairo',
      fontSize: 30,
      fontWeight: FontWeight.w700,
    ),
    titleMedium: TextStyle(
      fontSize: 18,
      color: Colors.black87,
    ),
    bodyMedium: TextStyle(
      fontSize: 16,
      color: Colors.black54,
    ),
    labelLarge: TextStyle(
      fontSize: 14,
      fontWeight: FontWeight.w600,
      color: Colors.white,
    ),
  ),
);
Dart
🧩 طريقة الاستخدام داخل الـ Widgets
✅ الطريقة الأولى — باستخدام Theme.of(context)
Text(
  "مرحباً بك في التطبيق!",
  style: Theme.of(context).textTheme.headlineLarge,
),
Dart
✅ الطريقة الثانية — تخصيص إضافي مؤقت
Text(
  "نص بتعديل بسيط",
  style: Theme.of(context).textTheme.bodyMedium!.copyWith(
        color: Colors.red,
        fontWeight: FontWeight.bold,
      ),
),
Dart
🪄 نصائح احترافية:
  1. يمكنك تغيير جميع النصوص بتعديل واحد فقط داخل TextTheme بدلًا من تعديل كل Text في التطبيق.
  2. استخدم مكتبة مثل GoogleFonts لسهولة تطبيق خطوط Google: textTheme: GoogleFonts.cairoTextTheme(),
  3. يمكنك إنشاء ملف منفصل مثل app_theme.dart يحتوي على إعداداتك الخاصة للمظهر (ThemeData) ثم استدعائه في MaterialApp.
💡 مثال نهائي متكامل في MaterialApp
MaterialApp(
  debugShowCheckedModeBanner: false,
  theme: appTheme,
  home: Scaffold(
    appBar: AppBar(title: Text("تجربة الخطوط")),
    body: Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text("Display Large", style: Theme.of(context).textTheme.displayLarge),
          Text("Headline Large", style: Theme.of(context).textTheme.headlineLarge),
          Text("Body Medium", style: Theme.of(context).textTheme.bodyMedium),
          ElevatedButton(
            onPressed: () {},
            child: Text("زر", style: Theme.of(context).textTheme.labelLarge),
          ),
        ],
      ),
    ),
  ),
);
Dart