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

Container و Padding و SizedBox

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

فهم Container

يُعد عنصر Container من أكثر العناصر تنوعاً في Flutter. فهو يجمع بين عناصر الرسم والتموضع والتحجيم الشائعة في عنصر واحد مريح. ومع ذلك، فإن فهم متى وكيف يستخدم Container كائنات عرض مختلفة خلف الكواليس هو المفتاح لكتابة تخطيطات فعالة.

نقطة مهمة: Container بدون عناصر فرعية يحاول أن يكون بأكبر حجم ممكن. Container مع عنصر فرعي يُحجّم نفسه حسب العنصر الفرعي. يتغير هذا السلوك عند إضافة قيود مثل العرض أو الارتفاع أو BoxConstraints.

عند تعيين خصائص مختلفة على Container، يقوم Flutter فعلياً بتركيب عناصر متعددة داخلياً. على سبيل المثال، تعيين color ينشئ ColoredBox، وتعيين padding يلف العنصر الفرعي في عنصر Padding، وتعيين alignment يلفه في Align، وتعيين decoration يستخدم DecoratedBox.

استكشاف Container بعمق

// Container بخصائص متعددة
// داخلياً يركّب: Align > Padding > DecoratedBox > ConstrainedBox
Container(
  alignment: Alignment.center,
  padding: const EdgeInsets.all(16.0),
  margin: const EdgeInsets.symmetric(horizontal: 20.0),
  decoration: BoxDecoration(
    color: Colors.blue.shade50,
    borderRadius: BorderRadius.circular(12.0),
    boxShadow: [
      BoxShadow(
        color: Colors.black.withOpacity(0.1),
        blurRadius: 8.0,
        offset: const Offset(0, 2),
      ),
    ],
  ),
  constraints: const BoxConstraints(
    minHeight: 100.0,
    maxWidth: 400.0,
  ),
  child: const Text('مرحباً Flutter!'),
)
تحذير: لا يمكنك تعيين كل من color و decoration على Container. إذا كنت بحاجة إلى لون خلفية مع decoration، قم بتضمين اللون داخل BoxDecoration باستخدام خاصية color.

Container بدون عنصر فرعي

عندما لا يحتوي Container على عنصر فرعي، يحاول أن يكون بأكبر حجم ممكن، مالئاً كل المساحة المتاحة من العنصر الأب. هذا مفيد لإنشاء خلفيات ملونة أو كتل زخرفية.

Container بدون عنصر فرعي

// هذا Container يملأ كل المساحة المتاحة
Scaffold(
  body: Container(
    color: Colors.blue,
    // بدون عنصر فرعي - يملأ جسم Scaffold بالكامل
  ),
)

// هذا Container له حجم ثابت
Container(
  width: 200.0,
  height: 100.0,
  color: Colors.red,
  // بدون عنصر فرعي لكن مقيّد بـ 200x100
)

عنصر Padding

بينما يحتوي Container على خاصية padding، يوفر Flutter أيضاً عنصر Padding مخصص. عندما تحتاج فقط إلى حشو حول عنصر فرعي، استخدام عنصر Padding أكثر كفاءة ووضوحاً من التغليف في Container.

عنصر Padding مقابل حشو Container

// استخدام عنصر Padding (مفضّل عند الحاجة للحشو فقط)
const Padding(
  padding: EdgeInsets.all(16.0),
  child: Text('نظيف وفعال'),
)

// استخدام Container للحشو فقط (مبذّر)
Container(
  padding: const EdgeInsets.all(16.0),
  child: const Text('يعمل لكن عبء غير ضروري'),
)

// أنماط حشو مختلفة
const Padding(
  padding: EdgeInsets.symmetric(
    horizontal: 24.0,
    vertical: 12.0,
  ),
  child: Text('حشو متماثل'),
)

const Padding(
  padding: EdgeInsets.only(
    left: 16.0,
    top: 8.0,
    right: 16.0,
    bottom: 24.0,
  ),
  child: Text('حشو اتجاهي'),
)

// للحشو الاتجاهي الآمن لـ RTL
const Padding(
  padding: EdgeInsetsDirectional.only(
    start: 16.0,
    end: 8.0,
  ),
  child: Text('حشو متوافق مع RTL'),
)
نصيحة: استخدم EdgeInsetsDirectional بدلاً من EdgeInsets عندما يدعم تطبيقك لغات LTR و RTL. يستخدم start و end بدلاً من left و right، مع التبديل تلقائياً لتخطيطات RTL.

أساسيات SizedBox

عنصر SizedBox هو صندوق بحجم محدد. يفرض على عنصره الفرعي عرضاً و/أو ارتفاعاً محدداً. عند استخدامه بدون عنصر فرعي، يعمل كفاصل غير مرئي — أحد أكثر أنماط التباعد شيوعاً في Flutter.

أساسيات SizedBox

// صندوق بحجم ثابت مع عنصر فرعي
const SizedBox(
  width: 200.0,
  height: 100.0,
  child: Card(
    child: Center(child: Text('بطاقة بحجم ثابت')),
  ),
)

// SizedBox كفاصل (نمط شائع جداً)
Column(
  children: const [
    Text('العنصر الأول'),
    SizedBox(height: 16.0),  // تباعد عمودي
    Text('العنصر الثاني'),
    SizedBox(height: 24.0),  // تباعد عمودي أكبر
    Text('العنصر الثالث'),
  ],
)

Row(
  children: const [
    Icon(Icons.star),
    SizedBox(width: 8.0),  // تباعد أفقي
    Text('تقييم بالنجوم'),
  ],
)

SizedBox.expand و SizedBox.fromSize

يوفر Flutter منشئات مسماة مريحة لأنماط SizedBox الشائعة:

منشئات SizedBox المسماة

// SizedBox.expand - يملأ كل المساحة المتاحة
// مكافئ لـ SizedBox(width: double.infinity, height: double.infinity)
const SizedBox.expand(
  child: ColoredBox(
    color: Colors.green,
    child: Center(child: Text('أملأ كل شيء!')),
  ),
)

// SizedBox.shrink - يأخذ أقل مساحة (0x0)
// مفيد كعنصر نائب أو عنصر فارغ
const SizedBox.shrink()  // width: 0, height: 0

// SizedBox.fromSize - إنشاء من كائن Size
SizedBox.fromSize(
  size: const Size(150.0, 75.0),
  child: const Placeholder(),
)

// SizedBox مع double.infinity لمحور واحد
const SizedBox(
  width: double.infinity,  // عرض كامل
  height: 50.0,            // ارتفاع ثابت
  child: ColoredBox(
    color: Colors.blue,
    child: Center(child: Text('شريط بعرض كامل')),
  ),
)

ConstrainedBox

بينما يحدد SizedBox حجماً دقيقاً، يتيح لك ConstrainedBox تحديد قيود الحد الأدنى والأقصى للعرض والارتفاع. هذا يمنحك تحكماً أكثر مرونة في التحجيم.

أمثلة ConstrainedBox

// تحديد أبعاد دنيا
ConstrainedBox(
  constraints: const BoxConstraints(
    minWidth: 100.0,
    minHeight: 50.0,
  ),
  child: const Text('100x50 على الأقل'),
)

// تحديد أبعاد قصوى
ConstrainedBox(
  constraints: const BoxConstraints(
    maxWidth: 300.0,
    maxHeight: 200.0,
  ),
  child: const Text('لا أكبر من 300x200'),
)

// الجمع بين الحد الأدنى والأقصى
ConstrainedBox(
  constraints: const BoxConstraints(
    minWidth: 100.0,
    maxWidth: 300.0,
    minHeight: 50.0,
    maxHeight: 150.0,
  ),
  child: Container(
    color: Colors.amber,
    child: const Text('مرن ضمن الحدود'),
  ),
)

// منشئات مسماة
ConstrainedBox(
  constraints: BoxConstraints.tight(const Size(200, 100)),
  child: const Placeholder(),  // بالضبط 200x100
)

ConstrainedBox(
  constraints: BoxConstraints.loose(const Size(300, 200)),
  child: const Placeholder(),  // حتى 300x200
)

FractionallySizedBox

يُحجّم عنصر FractionallySizedBox عنصره الفرعي كنسبة من المساحة المتاحة. هذا مفيد جداً للتخطيطات المتجاوبة حيث تريد أن تأخذ العناصر نسبة مئوية من حجم العنصر الأب.

أمثلة FractionallySizedBox

// أخذ 80% من عرض الأب و 50% من ارتفاعه
FractionallySizedBox(
  widthFactor: 0.8,
  heightFactor: 0.5,
  child: Container(
    color: Colors.purple.shade100,
    child: const Center(
      child: Text('80% عرض، 50% ارتفاع'),
    ),
  ),
)

// حاوية متجاوبة - 90% عرض على الشاشات الصغيرة
SizedBox(
  width: double.infinity,
  child: FractionallySizedBox(
    widthFactor: 0.9,
    child: Card(
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: const [
            Text('بطاقة متجاوبة'),
            SizedBox(height: 8.0),
            Text('تأخذ 90% من العرض المتاح'),
          ],
        ),
      ),
    ),
  ),
)

// FractionallySizedBox مع محاذاة
FractionallySizedBox(
  alignment: Alignment.topLeft,
  widthFactor: 0.6,
  heightFactor: 0.4,
  child: Container(
    color: Colors.teal,
    child: const Center(child: Text('أعلى اليسار 60%x40%')),
  ),
)

أمثلة عملية

نمط التباعد: فجوات متسقة

استخدم SizedBox لإنشاء تباعد متسق في جميع أنحاء تطبيقك. تحدد العديد من الفرق ثوابت تباعد للتوحيد:

نمط ثوابت التباعد

// تحديد ثوابت التباعد
abstract class AppSpacing {
  static const double xs = 4.0;
  static const double sm = 8.0;
  static const double md = 16.0;
  static const double lg = 24.0;
  static const double xl = 32.0;
  static const double xxl = 48.0;

  // فواصل SizedBox قابلة لإعادة الاستخدام
  static const SizedBox verticalXs = SizedBox(height: xs);
  static const SizedBox verticalSm = SizedBox(height: sm);
  static const SizedBox verticalMd = SizedBox(height: md);
  static const SizedBox verticalLg = SizedBox(height: lg);
  static const SizedBox horizontalSm = SizedBox(width: sm);
  static const SizedBox horizontalMd = SizedBox(width: md);
}

// الاستخدام في نموذج
Column(
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: [
    const Text('إنشاء حساب', style: TextStyle(fontSize: 24)),
    AppSpacing.verticalLg,
    const TextField(decoration: InputDecoration(labelText: 'الاسم')),
    AppSpacing.verticalMd,
    const TextField(decoration: InputDecoration(labelText: 'البريد الإلكتروني')),
    AppSpacing.verticalMd,
    const TextField(decoration: InputDecoration(labelText: 'كلمة المرور')),
    AppSpacing.verticalLg,
    ElevatedButton(
      onPressed: () {},
      child: const Text('التسجيل'),
    ),
  ],
)
نصيحة: إعلان فواصل SizedBox كحقول ثابتة const يعني أن Flutter يعيد استخدام نفس نسخة العنصر في كل مكان، مما يوفر الذاكرة ويحسن الأداء.

نمط الحاوية المتجاوبة

حاوية محتوى متجاوبة

class ResponsiveContainer extends StatelessWidget {
  final Widget child;
  final double maxWidth;

  const ResponsiveContainer({
    super.key,
    required this.child,
    this.maxWidth = 600.0,
  });

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ConstrainedBox(
        constraints: BoxConstraints(maxWidth: maxWidth),
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16.0),
          child: child,
        ),
      ),
    );
  }
}

// الاستخدام - المحتوى لا يتجاوز 600 بكسل لكن يملأ الشاشات الأصغر
Scaffold(
  body: ResponsiveContainer(
    child: Column(
      children: const [
        Text('هذا المحتوى قابل للقراءة على جميع أحجام الشاشات'),
        SizedBox(height: 16),
        Text('الحد الأقصى للعرض 600 بكسل، متمركز على الشاشات الكبيرة'),
      ],
    ),
  ),
)
ملخص: استخدم Container عندما تحتاج عدة خصائص (decoration و alignment و padding) معاً. استخدم Padding عندما تحتاج الحشو فقط. استخدم SizedBox للأحجام الثابتة والتباعد. استخدم ConstrainedBox لقيود الحد الأدنى/الأقصى. استخدم FractionallySizedBox للتحجيم بالنسب المئوية.