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
Dart3) كيف تسجل الخطوط في 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
),
)
Dart5) تعيين الخط على مستوى التطبيق كله (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,
),
)Dart7) دعم اللغات المتعددة (عربي + إنجليزي) وخيارات 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: 700Dartmain.dart (مقتطف):
ThemeData(
fontFamily: 'Inter',
textTheme: TextTheme(
headlineLarge: TextStyle(fontFamily: 'Cairo', fontSize: 30, fontWeight: FontWeight.w700),
bodyMedium: TextStyle(fontSize: 16),
),
)Dart12) نصائح تصميمية وأفضل الممارسات
- اختر عددًا مناسبًا من الأوزان فقط (مثلاً Regular, Medium, Bold) لتقليل حجم التطبيق.
- ضع الخطوط في
assets/fonts/لتنظيم أفضل. - استخدم
ThemeDataلتوحيد المظهر بدلًا من ضبطfontFamilyلكلText. - استخدم
fontFamilyFallbackعند الحاجة لدعم لغات متعددة. - اختبر على أجهزة فعلية لأن طريقة التظهير قد تختلف بين المنصات (iOS vs Android).
13) Checklist سريع للتأكد من عمل الخط
- هل وضعت ملفات الخط في المسار الصحيح؟
assets/fonts/... - هل أدرجتها في
pubspec.yamlتحتfonts:بدقة indentation؟ - هل استعملت نفس اسم
familyفيTextStyle(fontFamily: '...')أو فيThemeData؟ - هل قمت بعمل flutter clean ثم flutter pub get ثم restart إذا لم تظهر؟
- هل ملفات الخط تدعم الأوزان/الستايلات التي تستخدمها؟
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🪄 نصائح احترافية:
- يمكنك تغيير جميع النصوص بتعديل واحد فقط داخل
TextThemeبدلًا من تعديل كلTextفي التطبيق. - استخدم مكتبة مثل
GoogleFontsلسهولة تطبيق خطوط Google:textTheme: GoogleFonts.cairoTextTheme(), - يمكنك إنشاء ملف منفصل مثل
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