العناصر المتراكبة في Flutter: Stack و Positioned و Align
Row يرتب العناصر بجانب بعضها، و Column يرتبها تحت بعضها. لكن ماذا عن دائرة حمراء صغيرة فوق أيقونة الجرس؟ أو عنوان مكتوب فوق صورة؟ أو زر مثبت في زاوية بطاقة؟ هنا يأتي دور Stack: المحور الثالث في التصميم، من الخلف إلى الأمام.
فكرة Stack
Section titled “فكرة Stack”Stack يضع أبناءه فوق بعضهم كطبقات. تخيّل أوراقاً شفافة تضعها واحدة فوق الأخرى على المكتب: أول ورقة في الأسفل، وكل ورقة جديدة تغطي ما تحتها.
Stack( children: [ Container(width: 160, height: 160, color: Colors.indigo), Container(width: 110, height: 110, color: Colors.amber), Container(width: 60, height: 60, color: Colors.red), ],)الترتيب في الكود هو ترتيب الطبقات: أول ابن في الخلف، وآخر ابن في الأمام. وافتراضياً تتجمع العناصر كلها عند زاوية البداية العلوية (اليمين العلوي في التطبيق العربي).
محاذاة الكل: alignment
Section titled “محاذاة الكل: alignment”لتغيير مكان تجمّع الأبناء:
Stack( alignment: Alignment.center, children: [ Container(width: 160, height: 160, color: Colors.indigo), const Text('في المنتصف', style: TextStyle(color: Colors.white)), ],)Alignment.center تضع كل الأبناء في منتصف الـ Stack. ومن القيم الأخرى: topCenter و bottomCenter و centerLeft و bottomRight.
تحديد مكان ابن واحد: Positioned
Section titled “تحديد مكان ابن واحد: Positioned”alignment تحرّك كل الأبناء معاً. لتحديد مكان ابن بعينه، لفّه بـ Positioned واكتب بُعده عن الحواف:
Stack( children: [ Container(width: 200, height: 120, color: Colors.indigo.shade100), Positioned( top: 8, left: 8, child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), color: Colors.red, child: const Text('خصم 20%', style: TextStyle(color: Colors.white)), ), ), ],)topوbottomوleftوright: المسافة بين الابن وحافة الـ Stack.- إذا كتبت
leftوrightمعاً، يتمدد الابن بينهما. Positioned.fill: اختصار يجعل الابن يملأ الـ Stack كله.PositionedDirectional: نسخة تستخدمstartوendبدلاً منleftوright، لتحترم اتجاه اللغة.
تخيّل Positioned مثل دبوس تثبّت به ورقة صغيرة على لوحة: «على بعد 8 من الأعلى و 8 من اليسار».
Align: محاذاة ابن واحد
Section titled “Align: محاذاة ابن واحد”Align بديل أبسط عندما تريد وضع ابن في زاوية أو في المنتصف بدون أرقام:
Stack( children: [ Container(width: 200, height: 120, color: Colors.teal.shade100), const Align( alignment: Alignment.bottomCenter, child: Text('أسفل المنتصف'), ), ],)استخدم Align للأماكن المعروفة (زاوية، منتصف)، و Positioned عندما تحتاج مسافة دقيقة بالأرقام.
الخروج عن الحدود: clipBehavior
Section titled “الخروج عن الحدود: clipBehavior”افتراضياً يقص الـ Stack أي جزء يخرج عن حدوده. لكن شارة الإشعارات مثلاً تخرج قليلاً عن حدود الأيقونة عمداً. للسماح بذلك:
Stack( clipBehavior: Clip.none, children: [ const Icon(Icons.notifications, size: 36), Positioned( top: -4, right: -4, child: Container( padding: const EdgeInsets.all(4), decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), child: const Text( '3', style: TextStyle(color: Colors.white, fontSize: 12), ), ), ), ],)القيم السالبة في top و right تُخرج الدائرة عن حدود الأيقونة، و Clip.none تمنع قصها.
نص فوق صورة مع تعتيم
Section titled “نص فوق صورة مع تعتيم”النص الأبيض فوق صورة فاتحة لا يُقرأ. الحل الشائع: طبقة سوداء شبه شفافة بين الصورة والنص.
SizedBox( height: 180, child: Stack( fit: StackFit.expand, children: [ Image.asset('assets/images/cover.png', fit: BoxFit.cover), Container(color: Colors.black.withValues(alpha: 0.4)), const Center( child: Text( 'عروض الصيف', style: TextStyle(color: Colors.white, fontSize: 26), ), ), ], ),)fit: StackFit.expand تجعل كل الأبناء العاديين يتمددون بحجم الـ Stack، فتملأ الصورة وطبقة التعتيم المساحة كلها بدون أن تكتب أبعادهما.
المشروع: بطاقة منتج بشارات
Section titled “المشروع: بطاقة منتج بشارات”class ProductCard extends StatelessWidget { const ProductCard({super.key});
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('منتج اليوم')), body: Center( child: SizedBox( width: 240, height: 240, child: Stack( fit: StackFit.expand, children: [ ClipRRect( borderRadius: BorderRadius.circular(16), child: Image.asset('assets/images/cover.png', fit: BoxFit.cover), ), Positioned( top: 12, left: 12, child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: Colors.red, borderRadius: BorderRadius.circular(20), ), child: const Text( 'خصم 20%', style: TextStyle(color: Colors.white), ), ), ), Positioned( top: 4, right: 4, child: IconButton( onPressed: () {}, icon: const Icon(Icons.favorite_border, color: Colors.white), tooltip: 'أضف للمفضلة', ), ), Positioned( left: 0, right: 0, bottom: 0, child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.5), borderRadius: const BorderRadius.vertical( bottom: Radius.circular(16), ), ), child: const Text( 'سماعة لاسلكية', style: TextStyle(color: Colors.white, fontSize: 18), ), ), ), ], ), ), ), ); }}البطاقة أربع طبقات من الخلف إلى الأمام:
- الصورة تملأ البطاقة، مقصوصة بزوايا دائرية.
- شارة الخصم الحمراء، مثبتة في الزاوية العلوية اليسرى.
- زر القلب في الزاوية العلوية اليمنى.
- شريط سفلي شبه شفاف عليه اسم المنتج. كتبنا
left: 0وright: 0معاً ليتمدد بعرض البطاقة كله.
جرّب بنفسك
Section titled “جرّب بنفسك”- شغّل بطاقة المنتج وغيّر أماكن الشارات.
- حوّل البطاقة إلى
StatefulWidget، واجعل القلب يتحول إلىIcons.favoriteبلون أحمر عند الضغط. - أضف شارة عدد فوق أيقونة سلة التسوق باستخدام
Clip.noneوالقيم السالبة. - تحدٍّ: اصنع صورة شخصية دائرية في زاويتها السفلية نقطة خضراء صغيرة تعني «متصل الآن».
أخطاء شائعة
Section titled “أخطاء شائعة”- Stack كل أبنائه Positioned: لا يوجد ابن يحدد الحجم، فيأخذ الـ Stack أكبر مساحة متاحة أو ينكمش إلى صفر. ضع ابناً عادياً أو لف الـ Stack بـ
SizedBox. - استخدام Positioned خارج Stack: خطأ «Incorrect use of ParentDataWidget».
Positionedيعمل فقط كابن مباشر لـStack. - استخدام Stack لكل شيء: ترتيب عناصر متجاورة بالأرقام هش وينكسر مع تغيّر حجم الشاشة. استخدم
RowوColumnللترتيب، وStackللتراكب فقط.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
في Stack فيه ثلاثة أبناء، أي ابن يظهر في الأمام فوق الجميع؟
الأبناء يُرسمون بالترتيب: الأول في الخلف، وكل ابن بعده يُرسم فوقه، فالأخير في الأمام.
سؤال
كيف تجعل ابناً داخل Stack يتمدد بعرض الـ Stack كله في أسفله؟
تحديد left و right معاً يمدّ الابن بينهما، و bottom: 0 تلصقه بالحافة السفلية.
الخلاصة
Section titled “الخلاصة”Stackيرتب أبناءه كطبقات: الأول في الخلف والأخير في الأمام.alignmentتحاذي كل الأبناء، وPositionedوAlignلابن واحد.- حجم الـ Stack من أكبر ابن غير مثبَّت، و
StackFit.expandتمدد الأبناء العاديين. Clip.noneتسمح بخروج عنصر عن الحدود، مثل شارة الإشعارات.- طبقة سوداء شبه شفافة بين الصورة والنص تجعل النص مقروءاً.