تخطيطات Flutter والتصميم المتجاوب

محاذاة المحور الرئيسي والمحور المتقاطع

45 دقيقة الدرس 2 من 16

فهم المحاور في Flutter

كل Row و Column لديه محوران: المحور الرئيسي والمحور المتقاطع. فهم كيفية عمل هذه المحاور أمر أساسي للتحكم في موضع وتباعد العناصر الفرعية داخل عنصر التخطيط.

  • Row: المحور الرئيسي = أفقي (من اليسار لليمين)، المحور المتقاطع = عمودي (من الأعلى للأسفل)
  • Column: المحور الرئيسي = عمودي (من الأعلى للأسفل)، المحور المتقاطع = أفقي (من اليسار لليمين)

يوفر Flutter خاصيتين رئيسيتين للتحكم في المحاذاة على هذه المحاور: mainAxisAlignment و crossAxisAlignment.

ملاحظة: فكّر دائماً في العنصر الذي تستخدمه (Row أو Column) قبل تطبيق المحاذاة. نفس قيمة المحاذاة تُنتج نتائج بصرية مختلفة حسب ما إذا كان المحور الرئيسي أفقياً أم عمودياً.

MainAxisAlignment

تتحكم خاصية mainAxisAlignment في كيفية توزيع العناصر الفرعية على المحور الرئيسي. هناك ست خيارات:

MainAxisAlignment.start

يضع العناصر الفرعية في بداية المحور الرئيسي. هذه هي القيمة الافتراضية.

MainAxisAlignment.start

Row(
  mainAxisAlignment: MainAxisAlignment.start,
  children: <Widget>[
    Container(width: 60, height: 60, color: Colors.red),
    Container(width: 60, height: 60, color: Colors.green),
    Container(width: 60, height: 60, color: Colors.blue),
  ],
)
// النتيجة: [أحمر][أخضر][أزرق].................

MainAxisAlignment.end

يضع العناصر الفرعية في نهاية المحور الرئيسي.

MainAxisAlignment.end

Row(
  mainAxisAlignment: MainAxisAlignment.end,
  children: <Widget>[
    Container(width: 60, height: 60, color: Colors.red),
    Container(width: 60, height: 60, color: Colors.green),
    Container(width: 60, height: 60, color: Colors.blue),
  ],
)
// النتيجة: .................[أحمر][أخضر][أزرق]

MainAxisAlignment.center

يوسّط العناصر الفرعية على المحور الرئيسي.

MainAxisAlignment.center

Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Container(width: 60, height: 60, color: Colors.red),
    Container(width: 60, height: 60, color: Colors.green),
    Container(width: 60, height: 60, color: Colors.blue),
  ],
)
// النتيجة: ........[أحمر][أخضر][أزرق]........

MainAxisAlignment.spaceBetween

يوزع المساحة الإضافية بالتساوي بين العناصر الفرعية. يلامس العنصر الأول حافة البداية ويلامس العنصر الأخير حافة النهاية.

MainAxisAlignment.spaceBetween

Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  children: <Widget>[
    Container(width: 60, height: 60, color: Colors.red),
    Container(width: 60, height: 60, color: Colors.green),
    Container(width: 60, height: 60, color: Colors.blue),
  ],
)
// النتيجة: [أحمر]........[أخضر]........[أزرق]

MainAxisAlignment.spaceAround

يوزع المساحة الإضافية بالتساوي، مع نصف المساحة قبل العنصر الأول وبعد العنصر الأخير.

MainAxisAlignment.spaceAround

Row(
  mainAxisAlignment: MainAxisAlignment.spaceAround,
  children: <Widget>[
    Container(width: 60, height: 60, color: Colors.red),
    Container(width: 60, height: 60, color: Colors.green),
    Container(width: 60, height: 60, color: Colors.blue),
  ],
)
// النتيجة: ..[أحمر]....[أخضر]....[أزرق]..

MainAxisAlignment.spaceEvenly

يوزع المساحة الإضافية بالتساوي، بما في ذلك مساحة متساوية قبل العنصر الأول وبعد العنصر الأخير.

MainAxisAlignment.spaceEvenly

Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: <Widget>[
    Container(width: 60, height: 60, color: Colors.red),
    Container(width: 60, height: 60, color: Colors.green),
    Container(width: 60, height: 60, color: Colors.blue),
  ],
)
// النتيجة: ...[أحمر]...[أخضر]...[أزرق]...
نصيحة: الفرق بين spaceAround و spaceEvenly دقيق لكنه مهم. مع spaceAround، تباعد الحواف نصف التباعد بين العناصر. مع spaceEvenly، كل الفجوات متطابقة.

CrossAxisAlignment

تتحكم خاصية crossAxisAlignment في كيفية وضع العناصر الفرعية على المحور المتقاطع. هناك خمس خيارات:

CrossAxisAlignment.center (الافتراضي)

يوسّط العناصر الفرعية على المحور المتقاطع.

CrossAxisAlignment.center

Row(
  crossAxisAlignment: CrossAxisAlignment.center,
  children: <Widget>[
    Container(width: 60, height: 40, color: Colors.red),
    Container(width: 60, height: 80, color: Colors.green),
    Container(width: 60, height: 60, color: Colors.blue),
  ],
)

CrossAxisAlignment.start

يحاذي العناصر الفرعية عند بداية المحور المتقاطع (أعلى لـ Row، يسار لـ Column).

CrossAxisAlignment.start

Row(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: <Widget>[
    Container(width: 60, height: 40, color: Colors.red),
    Container(width: 60, height: 80, color: Colors.green),
    Container(width: 60, height: 60, color: Colors.blue),
  ],
)

CrossAxisAlignment.end

يحاذي العناصر الفرعية عند نهاية المحور المتقاطع (أسفل لـ Row، يمين لـ Column).

CrossAxisAlignment.end

Row(
  crossAxisAlignment: CrossAxisAlignment.end,
  children: <Widget>[
    Container(width: 60, height: 40, color: Colors.red),
    Container(width: 60, height: 80, color: Colors.green),
    Container(width: 60, height: 60, color: Colors.blue),
  ],
)

CrossAxisAlignment.stretch

يمدد العناصر الفرعية لملء المحور المتقاطع بالكامل. هذا مفيد للغاية لجعل كل العناصر بنفس الارتفاع في Row أو بنفس العرض في Column.

CrossAxisAlignment.stretch

SizedBox(
  height: 100,
  child: Row(
    crossAxisAlignment: CrossAxisAlignment.stretch,
    children: <Widget>[
      Container(width: 60, color: Colors.red),
      Container(width: 60, color: Colors.green),
      Container(width: 60, color: Colors.blue),
    ],
  ),
)
// كل الحاويات الثلاث أصبحت بارتفاع 100 بكسل
تحذير: عند استخدام CrossAxisAlignment.stretch، يجب أن يكون للعنصر الأب حجم محدود على المحور المتقاطع. بالنسبة لـ Row، يجب أن يكون للعنصر الأب ارتفاع محدد. بالنسبة لـ Column، يجب أن يكون للعنصر الأب عرض محدد. وإلا لن يتمكن Flutter من تحديد الحجم الذي يجب التمدد إليه.

CrossAxisAlignment.baseline

يحاذي العناصر الفرعية على خط الأساس النصي. هذا أساسي عندما يكون لديك نصوص بأحجام مختلفة يجب أن تصطف عند أسفل الحروف. يجب أيضاً تعيين خاصية textBaseline عند استخدام هذه المحاذاة.

CrossAxisAlignment.baseline

Row(
  crossAxisAlignment: CrossAxisAlignment.baseline,
  textBaseline: TextBaseline.alphabetic,
  children: <Widget>[
    Text('كبير', style: TextStyle(fontSize: 40)),
    Text('متوسط', style: TextStyle(fontSize: 24)),
    Text('صغير', style: TextStyle(fontSize: 14)),
  ],
)
// كل النصوص الثلاثة تتحاذى على خط الأساس

خاصية textBaseline

عند استخدام CrossAxisAlignment.baseline، يجب تحديد أي خط أساس تستخدم:

  • TextBaseline.alphabetic -- يحاذي عند أسفل الأحرف الأبجدية (الأكثر شيوعاً للخطوط اللاتينية والعربية والمشابهة).
  • TextBaseline.ideographic -- يحاذي عند أسفل الأحرف الإيديوغرافية (يُستخدم للخطوط الصينية واليابانية والكورية).
ملاحظة: إذا استخدمت CrossAxisAlignment.baseline بدون تعيين textBaseline، سيُطلق Flutter خطأ تأكيد أثناء التشغيل. احرص دائماً على إقرانهما معاً.

كيف تختلف المحاذاة بين Row و Column

نفس خاصية المحاذاة تتصرف بشكل مختلف حسب ما إذا كنت تستخدمها في Row أو Column لأن المحاور مُتبادلة:

نفس المحاذاة، عنصر مختلف

// في Row: العناصر الفرعية توسّط أفقياً
Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [Text('أ'), Text('ب'), Text('ج')],
)

// في Column: العناصر الفرعية توسّط عمودياً
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [Text('أ'), Text('ب'), Text('ج')],
)

// في Row: العناصر الفرعية تمتد عمودياً
Row(
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: [Container(width: 50, color: Colors.red)],
)

// في Column: العناصر الفرعية تمتد أفقياً
Column(
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: [Container(height: 50, color: Colors.red)],
)

مثال عملي: محتوى موسّط

نمط شائع هو توسيط المحتوى أفقياً وعمودياً على الشاشة:

محتوى موسّط بالكامل

Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Icon(Icons.check_circle, size: 80, color: Colors.green),
      SizedBox(height: 16),
      Text(
        'نجاح!',
        style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
      ),
      SizedBox(height: 8),
      Text(
        'تم تقديم طلبك بنجاح.',
        style: TextStyle(fontSize: 16, color: Colors.grey),
      ),
    ],
  ),
)

مثال عملي: تنقل متساوي التباعد

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

تخطيط شريط التنقل السفلي

Container(
  padding: EdgeInsets.symmetric(vertical: 8),
  decoration: BoxDecoration(
    color: Colors.white,
    boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 4)],
  ),
  child: Row(
    mainAxisAlignment: MainAxisAlignment.spaceAround,
    children: <Widget>[
      Column(
        mainAxisSize: MainAxisSize.min,
        children: [Icon(Icons.home, color: Colors.blue), Text('الرئيسية', style: TextStyle(fontSize: 12))],
      ),
      Column(
        mainAxisSize: MainAxisSize.min,
        children: [Icon(Icons.search, color: Colors.grey), Text('بحث', style: TextStyle(fontSize: 12))],
      ),
      Column(
        mainAxisSize: MainAxisSize.min,
        children: [Icon(Icons.favorite, color: Colors.grey), Text('المفضلة', style: TextStyle(fontSize: 12))],
      ),
      Column(
        mainAxisSize: MainAxisSize.min,
        children: [Icon(Icons.person, color: Colors.grey), Text('الملف', style: TextStyle(fontSize: 12))],
      ),
    ],
  ),
)

مثال عملي: نص محاذى على خط الأساس

عند عرض سعر مع عملة، يجب أن تتحاذى الأرقام ورمز العملة على خط الأساس:

عرض السعر بمحاذاة خط الأساس

Row(
  crossAxisAlignment: CrossAxisAlignment.baseline,
  textBaseline: TextBaseline.alphabetic,
  children: <Widget>[
    Text('\$', style: TextStyle(fontSize: 20, color: Colors.grey)),
    Text('49', style: TextStyle(fontSize: 48, fontWeight: FontWeight.bold)),
    Text('.99', style: TextStyle(fontSize: 20, color: Colors.grey)),
    Text('/شهر', style: TextStyle(fontSize: 14, color: Colors.grey)),
  ],
)

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

الملخص

  • MainAxisAlignment يتحكم في التوزيع على اتجاه التخطيط: start و end و center و spaceBetween و spaceAround و spaceEvenly.
  • CrossAxisAlignment يتحكم في التموضع العمودي على اتجاه التخطيط: start و end و center و stretch و baseline.
  • المحاور تتبادل بين Row (محور رئيسي أفقي) و Column (محور رئيسي عمودي).
  • استخدم CrossAxisAlignment.stretch لجعل كل العناصر الفرعية تملأ المحور المتقاطع -- يتطلب عنصراً أباً محدود الأبعاد.
  • استخدم CrossAxisAlignment.baseline مع textBaseline لمحاذاة نصوص بأحجام مختلفة.
  • spaceEvenly يوزع فجوات متساوية في كل مكان؛ spaceAround يعطي نصف فجوات عند الحواف؛ spaceBetween بدون فجوات عند الحواف.