stack 

ما هي Stack؟

ودجت Stack تسمح لك بوضع عناصر فوق بعضها (طبقات). تُستخدم في الحالات التي تحتاج فيها تراكبًا: شارة (Badge) فوق صورة، نص فوق بانر، زر يطفو بأسفل الشاشة… إلخ.

  • ترتيب الطبقات: آخر عنصر في قائمة children يُرسَم فوق ما قبله.
  • نوعا الأبناء:
    • Non-positioned: أبناء عاديون بدون Positioned، يتأثرون بالـ alignment وfit.
    • Positioned: أبناء يُحدَّد موقعهم بدقة باستخدام Positioned(...).
كيف يحدِّد Stack حجمه؟
  • إن كان فيه أبناء غير مُموضعين (non-positioned): يتّسع إلى أكبرهم (ضمن قيود الأب).
  • إن كان كل الأبناء Positioned: يأخذ أقصى حجم يسمح به الأب.
  • الخاصية fit تتحكم بقيود الأبناء غير المموضعين:
    • StackFit.loose (الافتراضي): قيود مرنة، يأخذ كل ابن حجمه “الطبيعي”.
    • StackFit.expand: يعطيهم قيودًا محكمة ليمتدّوا لملء الـ Stack.
    • StackFit.passthrough: يمرّر قيود الأب كما هي للأبناء.
أهم الخصائص
  • children: قائمة العناصر.
  • alignment: محاذاة الأبناء غير المموضعين داخل الـ Stack. (الافتراضي AlignmentDirectional.topStart؛ أي أعلى-بداية باحترام اتجاه اللغة).
  • fit: كما فصّلنا بالأعلى.
  • clipBehavior: كيف يتم القص عند خروج الطبقات خارج حدود الـ Stack.
    • شائع: Clip.hardEdge (افتراضي)، ويمكن Clip.none للسماح بالخروج (احذر من اللمس خارج الحدود).
  • textDirection: يؤثر على قيم AlignmentDirectional وPositionedDirectional (مهم للغات RTL).
التموضع (Positioning)

لاستخدام تموضع دقيق لعنصر داخل Stack:

  • Positioned(top:, right:, bottom:, left:, width:, height:)
    • لا تجمع بين (مثلاً) left وright وwidth معًا؛ اختر تركيبة منطقية.
  • Positioned.fill(...): يملأ كامل المساحة (مع إمكانية top/bottom/left/right كحواشي داخلية).
  • Align(...): بديل لطيف عندما تريد وضع عنصر غير مموضع عند موضع معيّن (وسط، أسفل يمين…) بدون حساب بكسلات.
أمثلة سريعة
1) تراكب نص فوق صورة
Stack(
  alignment: Alignment.center, // يؤثر على العنصر غير المموضع (النص هنا)
  children: [
    Image.asset('assets/banner.jpg', fit: BoxFit.cover),
    Text(
      'عنوان بانر',
      style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white),
    ),
  ],
)
Dart
2) شارة أعلى يمين الصورة (Positioned)
Stack(
  children: [
    ClipRRect(
      borderRadius: BorderRadius.circular(12),
      child: Image.network('https://picsum.photos/600/300', width: double.infinity, height: 180, fit: BoxFit.cover),
    ),
    Positioned(
      top: 8,
      right: 8,
      child: Container(
        padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
        decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(999)),
        child: Text('جديد', style: TextStyle(color: Colors.white)),
      ),
    ),
  ],
)
Dart
3) توسيع ابن غير مموضع لملء المساحة (fit: StackFit.expand)
Stack(
  fit: StackFit.expand, // يجعل الأبناء غير المموضعين يملؤون كامل المساحة
  children: [
    Image.asset('assets/bg.jpg', fit: BoxFit.cover),
    Container(color: Colors.black26), // طبقة تعتيم
    Center(child: Text('نص وسط الشاشة', style: TextStyle(color: Colors.white, fontSize: 20))),
  ],
)
Dart
4) زر يطفو أسفل يمين (Floating-like)
Stack(
  children: [
    // محتوى الصفحة
    ListView.builder(
      padding: EdgeInsets.only(bottom: 90), // إتاحة مساحة للزر
      itemCount: 30,
      itemBuilder: (_, i) => ListTile(title: Text('عنصر $i')),
    ),
    Positioned(
      right: 16,
      bottom: 16,
      child: FloatingActionButton(
        onPressed: () {},
        child: Icon(Icons.add),
      ),
    ),
  ],
)
Dart
5) استخدام Positioned.fill مع حواشي داخلية
Stack(
  children: [
    Container(color: Colors.blueGrey[50]),
    Positioned.fill(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: DecoratedBox(
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(16),
            gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [
              Colors.indigoAccent,
              Colors.blueAccent,
            ]),
          ),
        ),
      ),
    ),
    Align(
      alignment: Alignment.bottomCenter,
      child: Padding(
        padding: EdgeInsets.all(24),
        child: Text('لوحة معلومات', style: TextStyle(color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold)),
      ),
    ),
  ],
)
Dart
6) لـعرض طفل واحد مع الحفاظ على حالته: استخدم IndexedStack

عندما لديك عدّة شاشات داخل Stack وتريد إظهار واحدة فقط (مع الاحتفاظ بحالة الباقي):

IndexedStack(
  index: currentIndex, // أي طفل ظاهر الآن
  children: [
    ScreenA(),
    ScreenB(),
    ScreenC(),
  ],
)
Dart

IndexedStack يبني كل الأطفال لكنه لا يرسم إلا المحدّد بـ index—مفيد للتبويب (tabs) مع الحفاظ على الحالة.

ملاحظات مهمة (حالات شائعة)
  1. اللمس (Gesture) في وجود طبقات:
    • الطبقات العليا قد تمنع لمس ما تحتها.
    • للحفاظ على اللمس لما تحت طبقة شفافة، لفّ الطبقة الشفافة بـ IgnorePointer: IgnorePointer(child: YourOverlay())
  2. التمرير (Scroll) مع طبقة رأسية (Header) ثابتة:
    • اجعل الـ ListView لديه padding أسفل/أعلى، أو ضع رأسك في Positioned وثبّت أبعاده.
  3. القصّ (Clipping):
    • إن فقدت جزءًا خارج حدود Stack، جرّب clipBehavior: Clip.none (لكن تذكّر: قد لا تُسجَّل اللمسات خارج الحد).
  4. Expanded داخل Stack؟
    • Expanded لا يعمل إلا داخل Row/Column/Flex. في Stack استخدم Positioned.fill أو fit: StackFit.expand.
  5. الاتجاه RTL:
    • استخدم PositionedDirectional(start:, end:) أو AlignmentDirectional لاحترام اتجاه اللغة.
متى أستخدم Stack؟
  • شارة فوق صورة/أيقونة.
  • نص فوق بانر/فيديو.
  • زر يطفو على المحتوى.
  • طبقات تأثيرات (تعتيم، Blur، Gradient) فوق الخلفية.
  • بناء واجهة على شكل “طبقات” بدل الشبكية التقليدية.