ما هي Stack؟
ودجت Stack تسمح لك بوضع عناصر فوق بعضها (طبقات). تُستخدم في الحالات التي تحتاج فيها تراكبًا: شارة (Badge) فوق صورة، نص فوق بانر، زر يطفو بأسفل الشاشة… إلخ.
- ترتيب الطبقات: آخر عنصر في قائمة
childrenيُرسَم فوق ما قبله. - نوعا الأبناء:
- Non-positioned: أبناء عاديون بدون
Positioned، يتأثرون بالـalignmentوfit. - Positioned: أبناء يُحدَّد موقعهم بدقة باستخدام
Positioned(...).
- Non-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),
),
],
)
Dart2) شارة أعلى يمين الصورة (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)),
),
),
],
)
Dart3) توسيع ابن غير مموضع لملء المساحة (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))),
],
)
Dart4) زر يطفو أسفل يمين (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),
),
),
],
)
Dart5) استخدام 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)),
),
),
],
)
Dart6) لـعرض طفل واحد مع الحفاظ على حالته: استخدم IndexedStack
عندما لديك عدّة شاشات داخل Stack وتريد إظهار واحدة فقط (مع الاحتفاظ بحالة الباقي):
IndexedStack(
index: currentIndex, // أي طفل ظاهر الآن
children: [
ScreenA(),
ScreenB(),
ScreenC(),
],
)
Dart
IndexedStackيبني كل الأطفال لكنه لا يرسم إلا المحدّد بـindex—مفيد للتبويب (tabs) مع الحفاظ على الحالة.
ملاحظات مهمة (حالات شائعة)
- اللمس (Gesture) في وجود طبقات:
- الطبقات العليا قد تمنع لمس ما تحتها.
- للحفاظ على اللمس لما تحت طبقة شفافة، لفّ الطبقة الشفافة بـ
IgnorePointer:IgnorePointer(child: YourOverlay())
- التمرير (Scroll) مع طبقة رأسية (Header) ثابتة:
- اجعل الـ ListView لديه
paddingأسفل/أعلى، أو ضع رأسك فيPositionedوثبّت أبعاده.
- اجعل الـ ListView لديه
- القصّ (Clipping):
- إن فقدت جزءًا خارج حدود Stack، جرّب
clipBehavior: Clip.none(لكن تذكّر: قد لا تُسجَّل اللمسات خارج الحد).
- إن فقدت جزءًا خارج حدود Stack، جرّب
- Expanded داخل Stack؟
Expandedلا يعمل إلا داخلRow/Column/Flex. في Stack استخدمPositioned.fillأوfit: StackFit.expand.
- الاتجاه RTL:
- استخدم
PositionedDirectional(start:, end:)أوAlignmentDirectionalلاحترام اتجاه اللغة.
- استخدم
متى أستخدم Stack؟
- شارة فوق صورة/أيقونة.
- نص فوق بانر/فيديو.
- زر يطفو على المحتوى.
- طبقات تأثيرات (تعتيم، Blur، Gradient) فوق الخلفية.
- بناء واجهة على شكل “طبقات” بدل الشبكية التقليدية.