تخطَّ إلى المحتوى

تنسيق النصوص في Flutter: TextStyle و Text.rich

الدرس 9 من 33 · Flutter

تعرّفت على Text في درس الـ Widgets وكتبت بها نصوصاً بسيطة. لكن التطبيقات الحقيقية تحتاج أكثر: عنوان عريض، وسعر قديم مشطوب، وجملة فيها كلمة ملونة، ووصف طويل يُقص بثلاث نقاط. في هذا الدرس ستتعلم كل ما تحتاجه للتحكم في شكل النص.

كل ما يخص شكل النص يوضع داخل TextStyle:

const Text(
'عرض خاص لفترة محدودة',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.indigo,
letterSpacing: 0.5,
height: 1.6,
),
)
الخاصية وظيفتها
fontSize حجم الخط (الافتراضي 14)
fontWeight سُمك الخط: FontWeight.bold أو درجات من w100 إلى w900
color لون النص
fontStyle FontStyle.italic للخط المائل
letterSpacing المسافة بين الحروف
height ارتفاع السطر كمضاعف لحجم الخط (1.6 مريح للقراءة)
decoration خط تحت النص أو خط يشطبه
backgroundColor لون خلفية خلف الحروف فقط

خاصية decoration ترسم خطاً مع النص. أشهر استخدام لها هو السعر القديم المشطوب في المتاجر:

const Text(
'250 جنيهاً',
style: TextStyle(
decoration: TextDecoration.lineThrough,
decorationColor: Colors.red,
decorationThickness: 2,
color: Colors.grey,
),
),
const Text(
'اقرأ الشروط والأحكام',
style: TextStyle(decoration: TextDecoration.underline),
),
  • TextDecoration.lineThrough: خط يشطب النص.
  • TextDecoration.underline: خط تحت النص، مثل الروابط.
  • TextDecoration.overline: خط فوق النص.
  • decorationColor و decorationThickness: لون الخط وسُمكه.

ماذا يحدث إذا كان النص أطول من المساحة المتاحة؟ افتراضياً ينزل إلى سطر جديد. لكنك تستطيع التحكم في ذلك:

const Text(
'هذا وصف طويل جداً لمنتج في المتجر، ولن يظهر منه إلا سطران فقط، والباقي يُستبدل بثلاث نقاط.',
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
)
  • maxLines: أقصى عدد من الأسطر.
  • overflow: TextOverflow.ellipsis: يضع «…» مكان الجزء المقصوص. والبدائل: clip يقص بدون نقاط، و fade يجعل النهاية تتلاشى.
  • textAlign: المحاذاة. القيم start و end تحترم اتجاه اللغة، فـ TextAlign.start تعني اليمين في تطبيق عربي واليسار في تطبيق إنجليزي. لذلك فضّلها على left و right.

تخيّل maxLines مع ellipsis مثل عنوان خبر في الجريدة: المساحة محدودة، فيُكتب أول الخبر ثم «…» لتفهم أن له بقية.

تلوين جزء من الجملة: Text.rich

Section titled “تلوين جزء من الجملة: Text.rich”

Text العادية لها نمط واحد لكل النص. فكيف تكتب «السعر: 150 جنيهاً» والرقم وحده بلون مختلف؟ الحل Text.rich مع TextSpan:

const Text.rich(
TextSpan(
text: 'السعر: ',
children: [
TextSpan(
text: '150',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.green,
fontSize: 20,
),
),
TextSpan(text: ' جنيهاً'),
],
),
)

كل TextSpan قطعة من الجملة لها نمطها الخاص، والقطع كلها تُعرض في سطر واحد متصل كأنها نص واحد. والقطعة التي لا تحدد نمطاً ترث نمط القطعة الأم.

كتابة fontSize: 22 في كل مكان تجعل تطبيقك غير متناسق: عنوان بحجم 22 هنا و 24 هناك. الأفضل أن تستخدم الأنماط الجاهزة في ثيم التطبيق:

Text(
'عنوان الصفحة',
style: Theme.of(context).textTheme.headlineSmall,
),
Text(
'نص عادي داخل الصفحة',
style: Theme.of(context).textTheme.bodyLarge,
),

أهم الأنماط من الأكبر إلى الأصغر:

المجموعة الأنماط الاستخدام
headline headlineLarge و headlineMedium و headlineSmall عناوين الصفحات
title titleLarge و titleMedium و titleSmall عناوين البطاقات والأقسام
body bodyLarge و bodyMedium و bodySmall النص العادي
label labelLarge و labelMedium و labelSmall نصوص الأزرار والملصقات

وإذا أردت نمطاً جاهزاً مع تعديل صغير، استخدم copyWith:

Text(
'عنوان ملون',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Colors.indigo,
),
)

copyWith تنسخ النمط وتغيّر الخصائص التي تحددها فقط. وعلامة ?. موجودة لأن النمط قد يكون null، كما تعلمت في درس Null Safety.

لاستخدام خط عربي جميل مثل Cairo أو Tajawal:

  1. نزّل ملفات الخط (بامتداد .ttf) وضعها في مجلد assets/fonts/ داخل المشروع.
  2. عرّف الخط في pubspec.yaml:
pubspec.yaml
flutter:
uses-material-design: true
fonts:
- family: Cairo
fonts:
- asset: assets/fonts/Cairo-Regular.ttf
- asset: assets/fonts/Cairo-Bold.ttf
weight: 700
  1. استخدمه في نص واحد بـ fontFamily: 'Cairo' داخل TextStyle، أو اجعله خط التطبيق كله من الثيم:
MaterialApp(
theme: ThemeData(fontFamily: 'Cairo'),
home: const ArticleCard(),
)

لنجمع كل ما سبق في بطاقة تعرض مقالاً: تصنيف صغير، وعنوان عريض، ووصف يُقص بعد ثلاثة أسطر، وسطر معلومات فيه جزء ملون.

lib/article_card.dart
class ArticleCard extends StatelessWidget {
const ArticleCard({super.key});
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Scaffold(
appBar: AppBar(title: const Text('بطاقة مقال')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'برمجة',
style: textTheme.labelLarge?.copyWith(color: Colors.indigo),
),
const SizedBox(height: 8),
Text(
'لماذا يحب المبرمجون Flutter؟',
style: textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'لأنه يسمح لك بكتابة الكود مرة واحدة وتشغيله على أندرويد و iOS والويب، '
'ولأن التعديل يظهر أمامك في أقل من ثانية بفضل إعادة التحميل السريعة، '
'ولأن مكتبته مليئة بعناصر جاهزة جميلة تختصر عليك أسابيع من العمل.',
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: textTheme.bodyLarge?.copyWith(height: 1.7),
),
const SizedBox(height: 12),
const Text.rich(
TextSpan(
text: 'وقت القراءة: ',
children: [
TextSpan(
text: '4 دقائق',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.teal,
),
),
],
),
),
],
),
),
);
}
}

لاحظ في نص الوصف أننا كتبنا ثلاثة نصوص متتالية بدون أي علامة بينها. Dart تدمج النصوص المتجاورة تلقائياً في نص واحد، وهذه طريقة مريحة لتقسيم النص الطويل على عدة أسطر في الكود.

  1. شغّل بطاقة المقال، ثم غيّر maxLines إلى 1 وشاهد أين تظهر النقاط الثلاث.
  2. أضف تحت العنوان سعراً قديماً مشطوباً بجانبه سعر جديد أخضر عريض، باستخدام Text.rich.
  3. غيّر overflow إلى TextOverflow.fade ولاحظ الفرق.
  4. تحدٍّ: نزّل خطاً عربياً وعرّفه في pubspec.yaml، واجعله خط التطبيق كله.
  • نسيان const أو وضعها في غير مكانها: النص الذي يستخدم Theme.of(context) لا يمكن أن يكون const، لأن قيمته تُعرف وقت التشغيل. إذا ظهر خطأ «Not a constant expression» فاحذف const من ذلك الـ Widget.
  • استخدام TextAlign.left في تطبيق عربي: النص سيلتصق باليسار دائماً. استخدم TextAlign.start ليتبع اتجاه اللغة.
  • انتظار ظهور النقاط الثلاث بدون maxLines: overflow لا تعمل وحدها إذا كان النص حراً في النزول لأسطر جديدة. حدّد maxLines معها.

سؤال

كيف تجعل النص الطويل يظهر في سطرين فقط وينتهي بثلاث نقاط؟

سؤال

ما الوسيلة المناسبة لكتابة جملة واحدة فيها كلمة بلون مختلف؟

  • TextStyle تتحكم في الحجم والسُمك واللون وارتفاع السطر والشطب والتسطير.
  • maxLines مع TextOverflow.ellipsis لقص النص الطويل، و TextAlign.start للمحاذاة التي تحترم اتجاه اللغة.
  • Text.rich و TextSpan لتنسيق أجزاء مختلفة داخل نص واحد.
  • استخدم أنماط Theme.of(context).textTheme ليبقى تطبيقك متناسقاً، وعدّل عليها بـ copyWith.
  • الخط المخصص يُعرَّف في pubspec.yaml ثم يُفعَّل بـ fontFamily.