توثيق Layout Widgets في Flutter
فهم نظام التخطيط في Flutter: القيود والأحجام (Constraints and Sizes)
لفهم كيفية عمل الـ Layout Widgets في Flutter بشكل فعال، من الضروري استيعاب المبادئ الأساسية لنظام التخطيط. يعتمد Flutter على نموذج تخطيط قوي ومحدد يُعرف باسم "القيود تذهب لأسفل، والأحجام تذهب لأعلى" (Constraints go down, Sizes go up). هذا المبدأ يحكم كيفية تحديد حجم وموضع الـ widgets في شجرة الـ widgets.
مبدأ "Constraints go down, Sizes go up"
-
القيود تذهب لأسفل (Constraints go down):
- عندما يقوم الـ widget الأصل (parent widget) بتخطيط عنصر فرعي (child widget)، فإنه يمرر مجموعة من القيود (constraints) إلى هذا العنصر الفرعي. هذه القيود تحدد الحد الأدنى والأقصى للعرض والارتفاع الذي يمكن للعنصر الفرعي أن يشغله.
- لا يمكن للعنصر الفرعي أن يتجاوز هذه القيود. يجب أن يختار حجمًا يقع ضمن النطاق المحدد بواسطة الأصل.
-
الأحجام تذهب لأعلى (Sizes go up):
- بعد أن يتلقى العنصر الفرعي القيود من الأصل، يقوم هو نفسه بتحديد حجمه الخاص (size) بناءً على هذه القيود ومحتواه الخاص (إذا كان لديه محتوى).
- ثم يقوم العنصر الفرعي بإبلاغ الأصل بحجمه المختار. لا يمكن للعنصر الفرعي أن يفرض حجمًا على الأصل؛ بل يخبره فقط بالحجم الذي اختاره.
-
الموضع يحدده الأصل (Parent positions children):
- بمجرد أن يعرف الأصل حجم العنصر الفرعي، يكون مسؤولاً عن تحديد موضع هذا العنصر الفرعي داخل مساحته الخاصة.
الآثار المترتبة على هذا المبدأ:
- التخطيط من الأعلى للأسفل: يتم تحديد القيود من الأصل إلى العنصر الفرعي.
- تحديد الحجم من الأسفل للأعلى: يتم تحديد الحجم من العنصر الفرعي إلى الأصل.
- التحكم في الحجم: الـ widget الأصل هو الذي يحدد القيود، والعنصر الفرعي هو الذي يختار حجمه ضمن تلك القيود. لا يمكن للعنصر الفرعي أن يحدد حجمه بشكل مطلق دون مراعاة قيود الأصل.
مثال توضيحي:
تخيل Container (الأصل) يحتوي على Text (العنصر الفرعي):
Containerيمرر قيودًا إلىText(على سبيل المثال، الحد الأقصى للعرض هو 200 بكسل).Textيختار حجمه بناءً على هذه القيود ومحتواه (على سبيل المثال، إذا كان النص قصيرًا، فقد يختار عرضًا أقل من 200 بكسل).TextيبلغContainerبحجمه المختار.ContainerيضعTextداخل مساحته.
فهم هذا المبدأ أمر بالغ الأهمية لتجنب الأخطاء الشائعة مثل "RenderFlex overflowed" أو "BoxConstraints has non-finite width/height"، ولتصميم تخطيطات مرنة ومتجاوبة.
هذا المستند يقدم توثيقًا شاملاً لـ Layout Widgets في Flutter، مع التركيز على الخصائص الرئيسية والأمثلة البرمجية للـ widgets الأكثر استخدامًا، بالإضافة إلى نظرة عامة على الـ widgets الأخرى.
الـ Widgets الأساسية لتخطيط المحتوى
1. GridView
الوصف: GridView هي ويدجت تعرض عناصرها الفرعية (children) في شبكة ثنائية الأبعاد قابلة للتمرير. إنها واحدة من أكثر الـ widgets مرونة لعرض البيانات الشبكية، وتأتي مع مُحسِّنات أداء مدمجة للقوائم الطويلة.
أنواع GridView
GridView.count: الأبسط استخدامًا. ينشئ شبكة بعدد ثابت من الأعمدة (crossAxisCount).GridView.extent: ينشئ شبكة حيث يكون لكل عنصر حد أقصى للعرض (maxCrossAxisExtent). يتغير عدد الأعمدة تلقائيًا بناءً على عرض الشاشة، مما يجعله مثاليًا للتخطيطات المتجاوبة.GridView.builder: الخيار الأكثر كفاءة للقوائم الكبيرة أو اللانهائية. يقوم ببناء العناصر عند الطلب (lazily) أثناء التمرير، مما يوفر الذاكرة ووقت المعالجة.GridView.custom: يمنحك أقصى درجات التحكم، حيث تستخدمSliverChildDelegateمخصصًا لتحديد كيفية بناء العناصر.
الخواص الرئيسية (بتعمق):
gridDelegate: (مطلوب) يحدد هندسة الشبكة. هذا هو قلبGridView.SliverGridDelegateWithFixedCrossAxisCount: يُستخدم معGridView.countأوGridView.builder. يحدد عددًا ثابتًا من الأعمدة (crossAxisCount). يمكنك التحكم في نسبة العرض إلى الارتفاع (childAspectRatio) والمسافات (mainAxisSpacing,crossAxisSpacing).SliverGridDelegateWithMaxCrossAxisExtent: يُستخدم معGridView.extentأوGridView.builder. يحدد أقصى عرض مسموح به لكل عنصر (maxCrossAxisExtent). يقوم Flutter بحساب عدد الأعمدة الذي يمكن وضعه في المساحة المتاحة.
scrollDirection: اتجاه التمرير، إماAxis.vertical(افتراضي) أوAxis.horizontal.reverse: إذا كانتtrue، يتم عكس اتجاه التمرير ومحتوى الشبكة.controller:ScrollControllerللتحكم في موضع التمرير برمجيًا.primary: إذا كانتtrue، فإنGridViewستكون مرتبطة بـPrimaryScrollControllerالخاص بالتطبيق. يجب أن يكون هناكScrollViewأساسي واحد فقط لكلScaffold.physics: تحدد فيزياء التمرير. على سبيل المثال،BouncingScrollPhysics(تأثير الارتداد على iOS) أوClampingScrollPhysics(تأثير التوقف على Android).shrinkWrap: إذا كانتtrue، فإن حجمGridViewفي اتجاه التمرير سيتحدد بمجموع أحجام عناصرها. هذا مفيد عند وضعGridViewداخلColumn، ولكنه يأتي على حساب الأداء لأنه يتطلب حساب حجم جميع العناصر مرة واحدة.padding: مسافة بادئة حول منطقة التمرير بأكملها.itemBuilder: (لـGridView.builder) دالة تُستدعى لبناء كل عنصر في الشبكة. تتلقىBuildContextوindex.itemCount: (لـGridView.builder) العدد الإجمالي للعناصر في الشبكة.cacheExtent: يحدد مقدار المساحة خارج منفذ العرض (viewport) التي يجب أن يتم تخزينها مؤقتًا (caching). زيادة هذه القيمة يمكن أن تحسن أداء التمرير على حساب استخدام الذاكرة.
حالات الاستخدام (Use Cases)
- معارض الصور (Photo Galleries): عرض الصور في شبكة متساوية.
- قوائم المنتجات (Product Catalogs): عرض المنتجات مع صورها وأسعارها في تطبيقات التجارة الإلكترونية.
- لوحات التحكم (Dashboards): عرض بطاقات معلومات أو إحصائيات مختلفة.
- تطبيقات الموسيقى/الفيديو: عرض أغلفة الألبومات أو ملصقات الأفلام.
نصائح للأداء
- استخدم
GridView.builderدائمًا للقوائم التي تحتوي على أكثر من عدد قليل من العناصر. هذا يضمن أن العناصر التي تقع خارج الشاشة لا يتم بناؤها أو استهلاكها للذاكرة. - تجنب
shrinkWrap: trueمع القوائم الطويلة، لأنه يبطل ميزة البناء الكسول (lazy building) لـGridView.builder. - استخدم
constمع العناصر الفرعية (child widgets) التي لا تتغير لتمكين Flutter من إعادة استخدامها وتجنب إعادة بنائها بشكل غير ضروري.
مثال:
import 'package:flutter/material.dart';
class GridViewExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('GridView Example')),
body: GridView.builder(
padding: const EdgeInsets.all(10.0),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // عدد الأعمدة
crossAxisSpacing: 10.0, // المسافة بين الأعمدة
mainAxisSpacing: 10.0, // المسافة بين الصفوف
childAspectRatio: 1.0, // نسبة العرض إلى الارتفاع لكل عنصر
),
itemCount: 20,
itemBuilder: (BuildContext context, int index) {
return Container(
color: Colors.teal[100 * (index % 9)],
child: Center(
child: Text(
'Item $index',
style: TextStyle(fontSize: 20, color: Colors.white),
),
),
);
},
),
);
}
}
void main() {
runApp(MaterialApp(home: GridViewExample()));
}
2. ListView
الوصف: ListView هي ويدجت تعرض قائمة خطية قابلة للتمرير من الـ widgets. إنها الـ widget الأكثر استخدامًا للتمرير في Flutter وتوفر طرقًا متعددة لإنشاء قوائم فعالة.
أنواع ListView
ListView(الافتراضي): ينشئ قائمة من الـ widgets الفرعية الصريحة. مناسب للقوائم القصيرة التي لا يتغير عدد عناصرها كثيرًا.ListView.builder: الخيار الأكثر كفاءة للقوائم الطويلة أو اللانهائية. يقوم ببناء العناصر عند الطلب (lazily) أثناء التمرير، مما يحسن الأداء ويقلل استهلاك الذاكرة.ListView.separated: ينشئ قائمة مع فاصل (separator) بين كل عنصر وآخر. مفيد لإضافة خطوط فاصلة أو مسافات بين عناصر القائمة.ListView.custom: يمنحك أقصى درجات التحكم، حيث تستخدمSliverChildDelegateمخصصًا لتحديد كيفية بناء العناصر.
الخواص الرئيسية (بتعمق):
scrollDirection: اتجاه التمرير، إماAxis.vertical(افتراضي) أوAxis.horizontal.reverse: إذا كانتtrue، يتم عكس اتجاه التمرير ومحتوى القائمة.controller:ScrollControllerللتحكم في موضع التمرير برمجيًا (مثل التمرير إلى عنصر معين أو الاستماع إلى أحداث التمرير).primary: إذا كانتtrue، فإنListViewستكون مرتبطة بـPrimaryScrollControllerالخاص بالتطبيق. يجب أن يكون هناكScrollViewأساسي واحد فقط لكلScaffold.physics: تحدد فيزياء التمرير. على سبيل المثال،BouncingScrollPhysics(تأثير الارتداد على iOS) أوClampingScrollPhysics(تأثير التوقف على Android). يمكن أيضًا استخدامNeverScrollableScrollPhysicsلمنع التمرير.shrinkWrap: إذا كانتtrue، فإن حجمListViewفي اتجاه التمرير سيتحدد بمجموع أحجام عناصرها. هذا مفيد عند وضعListViewداخلColumnأوRow، ولكنه يأتي على حساب الأداء لأنه يتطلب حساب حجم جميع العناصر مرة واحدة.padding: مسافة بادئة حول منطقة التمرير بأكملها.itemExtent: إذا تم تعيين هذه الخاصية، فإن جميع العناصر في القائمة سيكون لها نفس الارتفاع (أو العرض إذا كانscrollDirectionأفقيًا). هذا يحسن الأداء بشكل كبير لأنه لا يتطلب من Flutter حساب حجم كل عنصر على حدة.prototypeItem: بديل لـitemExtent، حيث يتم استخدامprototypeItemلحساب حجم العناصر بشكل فعال دون الحاجة إلى تحديدitemExtentيدويًا.itemBuilder: (لـListView.builderوListView.separated) دالة تُستدعى لبناء كل عنصر في القائمة. تتلقىBuildContextوindex.separatorBuilder: (لـListView.separated) دالة تُستدعى لبناء الفاصل بين العناصر. تتلقىBuildContextوindex.itemCount: (لـListView.builderوListView.separated) العدد الإجمالي للعناصر في القائمة.cacheExtent: يحدد مقدار المساحة خارج منفذ العرض (viewport) التي يجب أن يتم تخزينها مؤقتًا (caching). زيادة هذه القيمة يمكن أن تحسن أداء التمرير على حساب استخدام الذاكرة.
حالات الاستخدام (Use Cases)
- قوائم الأخبار (News Feeds): عرض مقالات الأخبار أو منشورات المدونات.
- قوائم الدردشة (Chat Lists): عرض رسائل الدردشة.
- قوائم الإعدادات (Settings Menus): عرض خيارات الإعدادات المختلفة.
- قوائم المهام (To-Do Lists): عرض المهام القابلة للتمرير.
نصائح للأداء
- استخدم
ListView.builderدائمًا للقوائم التي تحتوي على عدد كبير من العناصر. هذا يضمن أن العناصر التي تقع خارج الشاشة لا يتم بناؤها أو استهلاكها للذاكرة. - تجنب
shrinkWrap: trueمع القوائم الطويلة، لأنه يبطل ميزة البناء الكسول (lazy building) لـListView.builder. - استخدم
itemExtentأوprototypeItemإذا كانت جميع عناصر القائمة لها نفس الحجم أو حجم مماثل. هذا يقلل بشكل كبير من تعقيد حساب التخطيط. - استخدم
constمع العناصر الفرعية (child widgets) التي لا تتغير لتمكين Flutter من إعادة استخدامها وتجنب إعادة بنائها بشكل غير ضروري. - تحسين
itemBuilder: تأكد من أن دالةitemBuilderخفيفة قدر الإمكان ولا تقوم بعمليات حسابية معقدة.
مثال:
import 'package:flutter/material.dart';
class ListViewExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('ListView Example')),
body: ListView.builder(
itemCount: 20,
itemBuilder: (BuildContext context, int index) {
return Card(
margin: EdgeInsets.all(8.0),
color: Colors.blue[100 * (index % 9)],
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'List Item $index',
style: TextStyle(fontSize: 22, color: Colors.white),
),
),
);
},
),
);
}
}
void main() {
runApp(MaterialApp(home: ListViewExample()));
}
3. Stack
الوصف: Stack هي ويدجت تسمح لك بتراكب (overlap) العديد من الـ widgets الفرعية فوق بعضها البعض. يتم وضع الـ widgets الفرعية في Stack بترتيب الرسم، حيث يتم رسم العنصر الأخير فوق العناصر السابقة. إنها مفيدة لإنشاء واجهات مستخدم معقدة تتطلب وضع عناصر فوق بعضها البعض، مثل أزرار الإجراءات العائمة (Floating Action Buttons) أو تراكبات الصور.
الخواص الرئيسية (بتعمق):
children: قائمة الـ widgets التي سيتم تراكبها. يمكن أن تكون هذه العناصر إماPositionedwidgets (التي تحدد موضعها بدقة) أوnon-positionedwidgets (التي يتم محاذاتها بواسطة خاصيةalignment).alignment: تحدد كيفية محاذاة العناصر الفرعية غير الموضعة (non-positioned children) داخل الـStack. القيمة الافتراضية هيAlignmentDirectional.topStart(أعلى اليسار في اللغات من اليسار لليمين، وأعلى اليمين في اللغات من اليمين لليسار). يمكن استخدام قيم مثلAlignment.center,Alignment.bottomRight، أو أيAlignmentمخصص.textDirection: يحدد اتجاه النص للعناصر الفرعية. يؤثر على كيفية تفسيرalignment.fit: تحدد كيفية توسيع العناصر الفرعية غير الموضعة في الـStack.StackFit.loose(افتراضي): يسمح للعناصر بتحديد حجمها الخاص، ولكنها لا تتجاوز حدود الـStack.StackFit.expand: يجبر العناصر الفرعية غير الموضعة على ملء الـStackبالكامل.
clipBehavior: تحدد كيفية التعامل مع العناصر الفرعية التي تتجاوز حدود الـStack.Clip.hardEdge(افتراضي) يقص العناصر الزائدة. يمكن استخدامClip.noneللسماح للعناصر بالظهور خارج الحدود، ولكن يجب استخدامه بحذر لأنه قد يؤدي إلى مشاكل في التخطيط أو الأداء.
Positioned Widget
تُستخدم Positioned widget داخل Stack لتحديد موضع عنصر فرعي بدقة باستخدام إحداثيات (top, bottom, left, right) أو أبعاد (width, height). يمكنها أن تأخذ قيمًا مطلقة أو نسبية.
الخواص الرئيسية لـ Positioned:
left,top,right,bottom: تحدد المسافة من حافة الـStack.width,height: تحدد عرض وارتفاع العنصر.
حالات الاستخدام (Use Cases)
- تراكب الصور والنصوص: وضع نص أو أيقونات فوق صورة.
- أزرار الإجراءات العائمة (Floating Action Buttons): وضع زر في زاوية الشاشة فوق المحتوى.
- الشارات (Badges): وضع شارة صغيرة (مثل عدد الإشعارات) فوق أيقونة.
- تخطيطات البطاقات المعقدة: إنشاء بطاقات تحتوي على عناصر متراكبة مثل صورة خلفية، نص عنوان، وأزرار.
نصائح للأداء
- استخدم
Positionedبحكمة: الإفراط في استخدامPositionedwidgets يمكن أن يجعل التخطيط معقدًا ويصعب صيانته. حاول استخدامalignmentلـStackقدر الإمكان للعناصر البسيطة. - تجنب
Clip.noneإلا إذا كنت متأكدًا من أن العناصر لن تتجاوز الحدود بشكل غير مرغوب فيه، حيث يمكن أن يؤثر على الأداء. - استخدم
constمع العناصر الفرعية الثابتة لتمكين Flutter من تحسين الأداء.
مثال:
import 'package:flutter/material.dart';
class StackExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Stack Example')),
body: Center(
child: Stack(
alignment: Alignment.center, // محاذاة العناصر في المنتصف
children: <Widget>[
Container(
width: 200,
height: 200,
color: Colors.red,
),
Container(
width: 150,
height: 150,
color: Colors.green,
),
Positioned(
bottom: 10,
right: 10,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
child: Center(child: Text('Blue', style: TextStyle(color: Colors.white))),
),
),
Text(
'Hello Stack',
style: TextStyle(color: Colors.white, fontSize: 24),
),
],
),
),
);
}
}
void main() {
runApp(MaterialApp(home: StackExample()));
}
4. Column
الوصف: Column هي ويدجت تعرض قائمة من الـ widgets الفرعية في اتجاه عمودي. إنها مفيدة لترتيب العناصر واحدًا فوق الآخر وتعتبر أساسية في بناء معظم واجهات المستخدم الرأسية.
الخواص الرئيسية (بتعمق):
children: قائمة الـ widgets التي سيتم ترتيبها عموديًا. يمكن أن تحتوي على أي عدد من الـ widgets.mainAxisAlignment: تحدد كيفية وضع العناصر الفرعية على المحور الرئيسي (main axis) (العمودي في هذه الحالة). تؤثر هذه الخاصية على توزيع المساحة الحرة على طول المحور الرئيسي.MainAxisAlignment.start: يبدأ وضع العناصر من بداية المحور الرئيسي.MainAxisAlignment.end: يبدأ وضع العناصر من نهاية المحور الرئيسي.MainAxisAlignment.center: يضع العناصر في منتصف المحور الرئيسي.MainAxisAlignment.spaceBetween: يوزع المساحة الحرة بالتساوي بين العناصر، مع ترك مسافة متساوية بين كل عنصر وآخر، ولا يترك مسافة قبل العنصر الأول أو بعد الأخير.MainAxisAlignment.spaceAround: يوزع المساحة الحرة بالتساوي حول العناصر، مع ترك مسافة متساوية قبل وبعد كل عنصر.MainAxisAlignment.spaceEvenly: يوزع المساحة الحرة بالتساوي بين العناصر وحولها، بحيث تكون المسافات متساوية تمامًا.
crossAxisAlignment: تحدد كيفية وضع العناصر الفرعية على المحور المتقاطع (cross axis) (الأفقي في هذه الحالة). تؤثر هذه الخاصية على محاذاة العناصر بالنسبة لبعضها البعض عبر المحور المتقاطع.CrossAxisAlignment.start: يحاذي العناصر إلى بداية المحور المتقاطع.CrossAxisAlignment.end: يحاذي العناصر إلى نهاية المحور المتقاطع.CrossAxisAlignment.center: يحاذي العناصر إلى منتصف المحور المتقاطع (افتراضي).CrossAxisAlignment.stretch: يمد العناصر لملء المحور المتقاطع بالكامل.CrossAxisAlignment.baseline: يحاذي العناصر بناءً على خط الأساس (baseline) الخاص بها (يتطلبtextBaseline).
mainAxisSize: تحدد مقدار المساحة التي يجب أن يشغلها الـColumnعلى المحور الرئيسي.MainAxisSize.max(افتراضي): يجعل الـColumnيملأ المساحة المتاحة على المحور الرئيسي.MainAxisSize.min: يجعل الـColumnيأخذ فقط المساحة التي يحتاجها لعرض عناصره على المحور الرئيسي.
crossAxisSize: تحدد مقدار المساحة التي يجب أن يشغلها الـColumnعلى المحور المتقاطع.CrossAxisSize.max(افتراضي): يجعل الـColumnيملأ المساحة المتاحة على المحور المتقاطع.
textDirection: يحدد اتجاه النص للعناصر الفرعية. يؤثر على كيفية تفسيرcrossAxisAlignment.verticalDirection: يحدد كيفية ترتيب العناصر الفرعية على المحور الرئيسي.VerticalDirection.down(افتراضي) يرتبها من الأعلى إلى الأسفل، وVerticalDirection.upيرتبها من الأسفل إلى الأعلى.
حالات الاستخدام (Use Cases)
- تخطيطات الشاشات العمودية: بناء معظم واجهات المستخدم التي تتطلب ترتيب العناصر عموديًا.
- بطاقات المعلومات (Info Cards): عرض عنوان، وصف، وأزرار بشكل عمودي.
- نماذج الإدخال (Forms): ترتيب حقول الإدخال والأزرار بشكل منظم.
- القوائم ذات العناصر المتنوعة: عندما تحتاج إلى عرض عناصر مختلفة لا تتناسب مع
ListViewالبسيط.
نصائح للأداء
- تجنب
ColumnداخلSingleChildScrollViewإذا كانColumnيحتوي على عدد كبير من العناصر، فقد يؤدي ذلك إلى مشاكل في الأداء لأنColumnيحاول بناء جميع عناصره مرة واحدة. في هذه الحالات، يفضل استخدامListView. - استخدم
mainAxisSize: MainAxisSize.minعندما لا تريد أن يشغل الـColumnالمساحة العمودية الكاملة المتاحة، خاصة عند وضعه داخلRowأوStack. - استخدم
constمع العناصر الفرعية الثابتة لتمكين Flutter من تحسين الأداء.
مثال:
import 'package:flutter/material.dart';
class ColumnExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Column Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(color: Colors.red, width: 100, height: 50),
Container(color: Colors.green, width: 150, height: 50),
Container(color: Colors.blue, width: 80, height: 50),
Text('Column Widget', style: TextStyle(fontSize: 20)),
],
),
),
);
}
}
void main() {
runApp(MaterialApp(home: ColumnExample()));
}
5. Row
الوصف: Row هي ويدجت تعرض قائمة من الـ widgets الفرعية في اتجاه أفقي. إنها مفيدة لترتيب العناصر جنبًا إلى جنب وتعتبر أساسية في بناء معظم واجهات المستخدم الأفقية.
الخواص الرئيسية (بتعمق):
children: قائمة الـ widgets التي سيتم ترتيبها أفقيًا. يمكن أن تحتوي على أي عدد من الـ widgets.mainAxisAlignment: تحدد كيفية وضع العناصر الفرعية على المحور الرئيسي (main axis) (الأفقي في هذه الحالة). تؤثر هذه الخاصية على توزيع المساحة الحرة على طول المحور الرئيسي.MainAxisAlignment.start: يبدأ وضع العناصر من بداية المحور الرئيسي.MainAxisAlignment.end: يبدأ وضع العناصر من نهاية المحور الرئيسي.MainAxisAlignment.center: يضع العناصر في منتصف المحور الرئيسي.MainAxisAlignment.spaceBetween: يوزع المساحة الحرة بالتساوي بين العناصر، مع ترك مسافة متساوية بين كل عنصر وآخر، ولا يترك مسافة قبل العنصر الأول أو بعد الأخير.MainAxisAlignment.spaceAround: يوزع المساحة الحرة بالتساوي حول العناصر، مع ترك مسافة متساوية قبل وبعد كل عنصر.MainAxisAlignment.spaceEvenly: يوزع المساحة الحرة بالتساوي بين العناصر وحولها، بحيث تكون المسافات متساوية تمامًا.
crossAxisAlignment: تحدد كيفية وضع العناصر الفرعية على المحور المتقاطع (cross axis) (العمودي في هذه الحالة). تؤثر هذه الخاصية على محاذاة العناصر بالنسبة لبعضها البعض عبر المحور المتقاطع.CrossAxisAlignment.start: يحاذي العناصر إلى بداية المحور المتقاطع.CrossAxisAlignment.end: يحاذي العناصر إلى نهاية المحور المتقاطع.CrossAxisAlignment.center: يحاذي العناصر إلى منتصف المحور المتقاطع (افتراضي).CrossAxisAlignment.stretch: يمد العناصر لملء المحور المتقاطع بالكامل.CrossAxisAlignment.baseline: يحاذي العناصر بناءً على خط الأساس (baseline) الخاص بها (يتطلبtextBaseline).
mainAxisSize: تحدد مقدار المساحة التي يجب أن يشغلها الـRowعلى المحور الرئيسي.MainAxisSize.max(افتراضي): يجعل الـRowيملأ المساحة المتاحة على المحور الرئيسي.MainAxisSize.min: يجعل الـRowيأخذ فقط المساحة التي يحتاجها لعرض عناصره على المحور الرئيسي.
crossAxisSize: تحدد مقدار المساحة التي يجب أن يشغلها الـRowعلى المحور المتقاطع.CrossAxisSize.max(افتراضي): يجعل الـRowيملأ المساحة المتاحة على المحور المتقاطع.
textDirection: يحدد اتجاه النص للعناصر الفرعية. يؤثر على كيفية تفسيرmainAxisAlignmentوcrossAxisAlignment.horizontalDirection: يحدد كيفية ترتيب العناصر الفرعية على المحور الرئيسي.TextDirection.ltr(افتراضي) يرتبها من اليسار إلى اليمين، وTextDirection.rtlيرتبها من اليمين إلى اليسار.
حالات الاستخدام (Use Cases)
- أشرطة الأدوات (Toolbars): ترتيب الأيقونات والأزرار أفقيًا.
- عناصر القائمة (List Items): عرض أيقونة، نص، وزر في صف واحد.
- رؤوس البطاقات (Card Headers): عرض صورة ملف شخصي واسم المستخدم جنبًا إلى جنب.
- تخطيطات التنقل (Navigation Layouts): ترتيب عناصر التنقل في شريط أفقي.
نصائح للأداء
- تجنب
RowداخلSingleChildScrollViewإذا كانRowيحتوي على عدد كبير من العناصر، فقد يؤدي ذلك إلى مشاكل في الأداء لأنRowيحاول بناء جميع عناصره مرة واحدة. في هذه الحالات، يفضل استخدامListViewالأفقي أوPageView. - استخدم
mainAxisSize: MainAxisSize.minعندما لا تريد أن يشغل الـRowالمساحة الأفقية الكاملة المتاحة، خاصة عند وضعه داخلColumnأوStack. - استخدم
constمع العناصر الفرعية الثابتة لتمكين Flutter من تحسين الأداء. - احذر من تجاوز المحتوى (Overflow): إذا كانت العناصر في
Rowتتجاوز المساحة المتاحة، فستحصل على خطأRenderFlex overflowed. استخدمExpandedأوFlexibleأوWrapللتعامل مع هذه الحالات بفعالية. مثال:
import 'package:flutter/material.dart';
class RowExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Row Example')),
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(color: Colors.red, width: 50, height: 100),
Container(color: Colors.green, width: 50, height: 150),
Container(color: Colors.blue, width: 50, height: 80),
Text('Row Widget', style: TextStyle(fontSize: 20)),
],
),
),
);
}
}
void main() {
runApp(MaterialApp(home: RowExample()));
}
الـ Widgets المساعدة لتخطيط المحتوى
6. SizedBox
الوصف: SizedBox هي ويدجت بسيطة وفعالة تفرض على عنصرها الفرعي (child) أن يكون له حجم محدد (عرض و/أو ارتفاع). إذا لم يكن لها عنصر فرعي، فإنها تشغل المساحة المحددة بنفسها، مما يجعلها مفيدة لإنشاء مسافات فارغة (spacers) أو تحديد أبعاد ثابتة.
الخواص الرئيسية (بتعمق):
width: العرض المطلوب للـSizedBox. إذا تم تحديده، فإنSizedBoxستحاول فرض هذا العرض على عنصرها الفرعي.height: الارتفاع المطلوب للـSizedBox. إذا تم تحديده، فإنSizedBoxستحاول فرض هذا الارتفاع على عنصرها الفرعي.child: العنصر الفرعي الذي سيتم فرض الحجم عليه. إذا لم يتم توفيرchild، فإنSizedBoxتعمل كـ spacer شفاف.
سلوك التخطيط (Layout Behavior)
- عندما يكون لها
child: تمررSizedBoxقيودًا صارمة (tight constraints) إلى عنصرها الفرعي بناءً علىwidthوheightالمحددين. هذا يعني أن العنصر الفرعي سيُجبر على أن يكون بنفس حجمSizedBox. - عندما لا يكون لها
child: تعملSizedBoxكصندوق فارغ بالحجم المحدد. يمكن استخدامها لإضافة مسافات أفقية أو عمودية ثابتة بين الـ widgets، وهي بديل شائع لـPaddingأوContainerلهذا الغرض عندما لا تكون هناك حاجة لخصائص أخرى.
حالات الاستخدام (Use Cases)
- إضافة مسافات ثابتة: إنشاء مسافات أفقية أو عمودية بين الـ widgets في
RowأوColumn. - تحديد حجم ثابت: فرض عرض وارتفاع محددين على ويدجت معينة، مثل صورة أو أيقونة.
- تحديد حجم
AppBarأوBottomNavigationBar: يمكن استخدامSizedBox.fromHeightأوSizedBox.fromWidthلتحديد أبعاد هذه العناصر.
نصائح للأداء
- استخدم
SizedBoxبدلاً منContainerالفارغ لإنشاء مسافات أو تحديد أحجام بسيطة، حيث أنSizedBoxأخف وأكثر كفاءة في الأداء. - استخدم
const SizedBox(...)عندما تكون الأبعاد ثابتة لتمكين Flutter من تحسين الأداء.
مثال:
import 'package:flutter/material.dart';
class SizedBoxExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SizedBox Example')),
body: Center(
child: Column(
children: <Widget>[
SizedBox(
width: 100,
height: 100,
child: Container(color: Colors.red, child: Center(child: Text('100x100'))),
),
SizedBox(height: 20), // مسافة فارغة بين العناصر
Container(
color: Colors.blue,
width: 50,
height: 50,
child: Center(child: Text('Blue Box')),
),
],
),
),
);
}
}
void main() {
runApp(MaterialApp(home: SizedBoxExample()));
}
7. Expanded
الوصف: Expanded هي ويدجت تستخدم داخل Row أو Column أو Flex لتوسيع عنصر فرعي بحيث يملأ المساحة المتاحة على المحور الرئيسي (main axis). إنها مفيدة لتوزيع المساحة بالتساوي أو بنسب محددة بين العناصر، وتعتبر حلاً أساسيًا للتعامل مع المساحات المرنة في التخطيطات الخطية.
الخواص الرئيسية (بتعمق):
flex: عامل المرونة (flex factor). يحدد مقدار المساحة المتاحة التي يجب أن يشغلها الـExpandedمقارنة بالـExpandedالأخرى. القيمة الافتراضية هي 1. على سبيل المثال، إذا كان لديكExpandedواحد بـflex: 1وآخر بـflex: 2، فإن الثاني سيشغل ضعف المساحة التي يشغلها الأول.child: العنصر الفرعي الذي سيتم توسيعه. يتم فرض قيود صارمة على هذا العنصر الفرعي لملء المساحة المخصصة له بواسطةExpanded.
سلوك التخطيط (Layout Behavior)
- داخل
RowأوColumn: عندما يتم وضعExpandedداخلRowأوColumn، فإنها تأخذ المساحة المتبقية على المحور الرئيسي بعد أن يتم تحديد حجم العناصر غير المرنة (non-flexible children). - القيود (Constraints): تمرر
Expandedقيودًا صارمة إلى عنصرها الفرعي. على المحور الرئيسي، يتم إعطاء العنصر الفرعي الحد الأقصى للمساحة المتاحة. على المحور المتقاطع، يتم إعطاء العنصر الفرعي نفس قيود الأصل (أيRowأوColumn). - التجاوز (Overflow): استخدام
Expandedيساعد على منع تجاوز المحتوى (overflow) فيRowأوColumnعن طريق السماح للعناصر بالتوسع لملء المساحة المتاحة بدلاً من تجاوزها.
الفرق بين Expanded و Flexible
Expanded: تجبر العنصر الفرعي على ملء المساحة المتاحة بالكامل على المحور الرئيسي. لا يمكن للعنصر الفرعي أن يكون أصغر من المساحة المخصصة له.Flexible: تسمح للعنصر الفرعي بالتوسع لملء المساحة المتاحة، ولكنها لا تجبره على ذلك. يمكن للعنصر الفرعي أن يكون أصغر من المساحة المخصصة له إذا كان حجمه الجوهري (intrinsic size) أصغر.Flexibleلديها خاصيةfitالتي يمكن أن تكونFlexFit.tight(مماثلة لـExpanded) أوFlexFit.loose(تسمح للعنصر بأن يكون أصغر).
حالات الاستخدام (Use Cases)
- توزيع المساحة بالتساوي: تقسيم المساحة المتاحة بين عدة عناصر في
RowأوColumn. - منع تجاوز المحتوى: التأكد من أن العناصر النصية الطويلة أو الصور الكبيرة لا تتجاوز حدود
RowأوColumn. - إنشاء تخطيطات مرنة: تصميم واجهات مستخدم تتكيف مع أحجام الشاشات المختلفة.
نصائح للأداء
- استخدم
ExpandedأوFlexibleدائمًا داخلRowأوColumnعندما تحتاج إلى توزيع المساحة بشكل مرن. تجنب تحديد أحجام ثابتة بشكل مفرط. - استخدم
constمع العناصر الفرعية الثابتة لتمكين Flutter من تحسين الأداء. - فهم سلوك
flex: استخدم قيمflexبعناية لتحقيق التوزيع المطلوب للمساحة.
مثال:
import 'package:flutter/material.dart';
class ExpandedExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Expanded Example')),
body: Center(
child: Column(
children: <Widget>[
Expanded(
flex: 2,
child: Container(color: Colors.red, child: Center(child: Text('Flex 2'))),
),
Expanded(
flex: 1,
child: Container(color: Colors.green, child: Center(child: Text('Flex 1'))),
),
Container(color: Colors.blue, height: 50, child: Center(child: Text('Fixed Height'))),
],
),
),
);
}
}
void main() {
runApp(MaterialApp(home: ExpandedExample()));
}
8. Center
الوصف: Center هي ويدجت تقوم بتوسيط عنصرها الفرعي (child) داخل نفسها. إنها تحاول أن تكون كبيرة بقدر الإمكان في كلا الاتجاهين (تأخذ أكبر مساحة ممكنة من الأصل)، ثم تقوم بتوسيط العنصر الفرعي داخل تلك المساحة. إنها مفيدة جدًا لوضع عنصر واحد في منتصف الشاشة أو أي مساحة متاحة.
الخواص الرئيسية (بتعمق):
child: العنصر الفرعي الذي سيتم توسيطه. يمكن أن يكون أي ويدجت.
سلوك التخطيط (Layout Behavior)
- القيود (Constraints): عندما تتلقى
Centerقيودًا من الأصل، فإنها تمرر هذه القيود إلى عنصرها الفرعي. ثم يحدد العنصر الفرعي حجمه الخاص ضمن هذه القيود. - تحديد الحجم: بعد أن يحدد العنصر الفرعي حجمه، تقوم
Centerبتحديد حجمها الخاص ليكون بنفس حجم الأصل (أي أنها تحاول أن تكون كبيرة بقدر الإمكان). ثم تقوم بحساب الموضع المناسب للعنصر الفرعي ليكون في المنتصف تمامًا. - الفرق مع
Align: بينما تقومCenterدائمًا بتوسيط العنصر الفرعي، فإنAlignتسمح لك بتحديد محاذاة مخصصة (مثلtopLeft,bottomRight, إلخ).Centerهي في الأساسAlignمعalignment: Alignment.center.
حالات الاستخدام (Use Cases)
- توسيط عنصر واحد: وضع زر، نص، أو صورة في منتصف الشاشة.
- توسيط محتوى داخل
Container: إذا كان لديكContainerبحجم معين وتريد توسيط محتوى داخله. - توسيط رسائل الخطأ أو التحميل: عرض رسالة "لا توجد بيانات" أو مؤشر تحميل في منتصف الشاشة.
نصائح للأداء
- لا تفرط في استخدام
Center: إذا كان لديكColumnأوRowوتريد توسيط جميع العناصر، فمن الأفضل استخدامmainAxisAlignment: MainAxisAlignment.centerوcrossAxisAlignment: CrossAxisAlignment.centerفي الـColumnأوRowنفسها بدلاً من تغليف كل عنصر بـCenter. - استخدم
constمع العناصر الفرعية الثابتة لتمكين Flutter من تحسين الأداء. - فهم القيود: تذكر أن
Centerتحاول أن تكون كبيرة بقدر الإمكان. إذا وضعتها داخل ويدجت لا تفرض قيودًا صارمة (مثلUnconstrainedBox)، فقد تتصرف بشكل غير متوقع.
مثال:
import 'package:flutter/material.dart';
class CenterExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Center Example')),
body: Center(
child: Container(
width: 200,
height: 200,
color: Colors.grey[300],
child: Center(
child: Container(
width: 100,
height: 100,
color: Colors.purple,
child: Center(child: Text('Centered', style: TextStyle(color: Colors.white))),
),
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: CenterExample()));
}
9. Padding
الوصف: Padding هي ويدجت تقوم بإضافة مسافة بادئة (padding) حول عنصرها الفرعي (child). يتم استخدامها لإنشاء مساحة بيضاء حول الـ widgets، وهي ضرورية للتحكم في التباعد بين العناصر وتحسين قابلية القراءة وجمالية التصميم.
الخواص الرئيسية (بتعمق):
padding: (مطلوب) تحدد مقدار المسافة البادئة التي سيتم تطبيقها. هذه الخاصية تأخذ كائنEdgeInsetsGeometry، والذي يوفر عدة طرق لتحديد المسافة البادئة:EdgeInsets.all(double value): يطبق مسافة بادئة متساوية من جميع الجوانب (أعلى، أسفل، يسار، يمين).EdgeInsets.symmetric({double vertical = 0.0, double horizontal = 0.0}): يطبق مسافة بادئة متساوية عموديًا وأفقيًا.EdgeInsets.only({double left = 0.0, double top = 0.0, double right = 0.0, double bottom = 0.0}): يطبق مسافة بادئة لجوانب محددة فقط.EdgeInsets.fromLTRB(double left, double top, double right, double bottom): يطبق مسافة بادئة من اليسار، الأعلى، اليمين، والأسفل بشكل منفصل.EdgeInsetsDirectional.only(...): مشابه لـEdgeInsets.onlyولكنه يحترم اتجاه النص (مثلstartبدلاً منleft).
child: العنصر الفرعي الذي سيتم تطبيق المسافة البادئة عليه. يتم وضع هذا العنصر داخل المساحة التي تحددهاPadding.
سلوك التخطيط (Layout Behavior)
- القيود (Constraints): تتلقى
Paddingقيودًا من الأصل، ثم تقوم بتقليص هذه القيود بمقدار المسافة البادئة المحددة قبل تمريرها إلى عنصرها الفرعي. هذا يعني أن العنصر الفرعي سيتم تخطيطه داخل المساحة المتبقية بعد تطبيق المسافة البادئة. - تحديد الحجم: بعد أن يحدد العنصر الفرعي حجمه، تقوم
Paddingبتحديد حجمها الخاص ليكون حجم العنصر الفرعي بالإضافة إلى المسافة البادئة المطبقة.
حالات الاستخدام (Use Cases)
- إضافة مسافات داخلية: توفير مساحة حول النصوص، الأيقونات، أو الصور داخل بطاقة (Card) أو حاوية (Container).
- تحسين قابلية القراءة: فصل الكتل النصية أو العناصر المرئية عن بعضها البعض.
- إنشاء تباعد موحد: الحفاظ على تباعد ثابت بين عناصر واجهة المستخدم.
نصائح للأداء
- استخدم
const EdgeInsets: إذا كانت قيم المسافة البادئة ثابتة، استخدمconstمعEdgeInsetsلتمكين Flutter من تحسين الأداء وإعادة استخدام الكائن. - تجنب
Paddingالمتداخلة بشكل مفرط: في بعض الحالات، قد يكون من الأفضل استخدامContainerمع خاصيةpaddingبدلاً من تغليف ويدجت بـPaddingثم تغليفها بـPaddingأخرى، لتقليل عدد الـ widgets في شجرة التخطيط. - فهم تأثير
Paddingعلى القيود: تذكر أنPaddingتقلل من القيود المتاحة للعنصر الفرعي. إذا كان العنصر الفرعي يحتاج إلى مساحة أكبر مما تسمح به القيود بعد تطبيق المسافة البادئة، فقد يحدث تجاوز (overflow).
مثال:
import 'package:flutter/material.dart';
class PaddingExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Padding Example')),
body: Center(
child: Container(
color: Colors.blueGrey,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Container(
color: Colors.orange,
width: 150,
height: 150,
child: Center(child: Text('Padded Content', style: TextStyle(color: Colors.white))),
),
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: PaddingExample()));
}
10. Container
الوصف: Container هي ويدجت مريحة تجمع بين الـ widgets الشائعة للرسم، التموضع، وتحديد الحجم. إنها ويدجت متعددة الاستخدامات للغاية يمكن استخدامها لإنشاء مجموعة واسعة من التأثيرات المرئية والتخطيطات المعقدة. يمكنها أن تحتوي على لون، زخرفة (decoration)، هوامش (margin)، مسافة بادئة (padding)، قيود (constraints)، وتحويلات (transformations).
الخواص الرئيسية (بتعمق):
alignment: كيفية محاذاة العنصر الفرعي داخل الـContainer. تأخذ قيمة من نوعAlignmentGeometry(مثلAlignment.center,Alignment.topLeft).padding: المسافة البادئة الداخلية (internal padding) داخل الـContainer، بين حدود الـContainerوعنصرها الفرعي. تأخذ قيمة من نوعEdgeInsetsGeometry.color: لون خلفية الـContainer. ملاحظة هامة: لا يمكن استخدامcolorمع خاصيةdecoration. إذا كنت تستخدمdecoration(مثلBoxDecoration)، فيجب تحديد اللون داخل الـBoxDecoration.decoration: زخرفة لتلوين أو تزيين الـContainerخلف العنصر الفرعي. غالبًا ما تستخدمBoxDecorationلإنشاء حدود، ألوان متدرجة، ظلال، أشكال، وصور خلفية.foregroundDecoration: زخرفة يتم رسمها فوق العنصر الفرعي. مفيدة لإضافة تراكبات (overlays) أو تأثيرات على العنصر الفرعي دون التأثير على تخطيطه.width: عرض الـContainerالمطلوب. إذا لم يتم تحديده، سيحاولContainerأن يكون كبيرًا بقدر الإمكان إذا كان غير مقيد، أو يأخذ حجم العنصر الفرعي إذا كان مقيدًا.height: ارتفاع الـContainerالمطلوب. نفس سلوكwidth.constraints: قيود إضافية على حجم الـContainer. تأخذ قيمة من نوعBoxConstraints. يمكن استخدامها لتحديد الحد الأدنى/الأقصى للعرض والارتفاع بشكل أكثر دقة منwidthوheight.margin: الهوامش الخارجية (external margin) حول الـContainer، بين الـContainerوالأصل (parent). تأخذ قيمة من نوعEdgeInsetsGeometry.transform: تحويل (transformation) يتم تطبيقه على الـContainerقبل الرسم. يمكن استخدامه للتدوير، التحجيم، والإزاحة. يأخذ قيمة من نوعMatrix4.transformAlignment: نقطة الأصل للتحويل (origin of the transform).child: العنصر الفرعي للـContainer. يمكن أن يكون أي ويدجت.clipBehavior: تحدد كيفية التعامل مع المحتوى الذي يتجاوز حدود الـContainerعند تطبيقdecorationأوtransform.
سلوك التخطيط (Layout Behavior)
Container هي ويدجت "تأخذ كل شيء" (greedy) في Flutter. سلوكها في التخطيط يعتمد بشكل كبير على الخصائص التي تم تعيينها:
- إذا كان لديها
childوwidth/heightأوconstraints: ستحاولContainerفرض هذه الأبعاد على العنصر الفرعي. إذا كانت القيود صارمة، سيأخذ العنصر الفرعي هذا الحجم. - إذا كان لديها
childولكن لا يوجدwidth/heightأوconstraints: ستحاولContainerأن تكون كبيرة بقدر الإمكان (إذا كانت غير مقيدة) ثم تطلب من العنصر الفرعي تحديد حجمه. بعد ذلك، ستقومContainerبتحديد حجمها ليكون بنفس حجم العنصر الفرعي. - إذا لم يكن لديها
childولكن لديهاwidth/heightأوconstraints: ستحاولContainerأن تأخذ الحجم المحدد. - إذا لم يكن لديها
childولاwidth/heightولاconstraints: ستحاولContainerأن تكون كبيرة بقدر الإمكان (إذا كانت غير مقيدة)، وإلا فإنها ستكون بحجم صفر.
حالات الاستخدام (Use Cases)
- تغليف الـ widgets: إضافة خلفية، حدود، أو ظلال لأي ويدجت.
- إنشاء بطاقات (Cards): استخدام
BoxDecorationلإنشاء بطاقات ذات زوايا مستديرة وظلال. - تحديد الأبعاد: فرض عرض وارتفاع محددين على منطقة معينة في واجهة المستخدم.
- إضافة هوامش ومسافات بادئة: التحكم في التباعد الخارجي والداخلي للعناصر.
- تطبيق التحويلات: تدوير أو تحجيم أو إزاحة الـ widgets.
نصائح للأداء
- استخدم
Containerبحكمة: نظرًا لأنContainerتجمع العديد من الخصائص، فإنها قد تكون أثقل قليلاً من الـ widgets المتخصصة (مثلSizedBoxللمسافات أوPaddingللمسافات البادئة). استخدم الـ widgets الأكثر تخصصًا عندما يكون ذلك ممكنًا لتحسين الأداء. - تجنب
colorوdecorationمعًا: تذكر أن استخدامcolorوdecorationفي نفسContainerسيؤدي إلى خطأ. حدد اللون داخلBoxDecorationإذا كنت تستخدمها. - استخدم
constمعContainerعندما تكون خصائصها ثابتة لتمكين Flutter من تحسين الأداء.
مثال:
import 'package:flutter/material.dart';
class ContainerExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Container Example')),
body: Center(
child: Container(
margin: EdgeInsets.all(20),
padding: EdgeInsets.all(30),
width: 250,
height: 250,
decoration: BoxDecoration(
color: Colors.cyan,
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.4),
spreadRadius: 5,
blurRadius: 10,
offset: Offset(0, 5),
),
],
),
child: Text(
'Hello from Container!',
style: TextStyle(color: Colors.white, fontSize: 22),
textAlign: TextAlign.center,
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: ContainerExample()));
}
11. Align
الوصف: Align هي ويدجت تقوم بمحاذاة عنصرها الفرعي (child) داخل نفسها، ويمكنها اختياريًا تغيير حجمها بناءً على حجم العنصر الفرعي. إنها مفيدة لوضع عنصر فرعي في مكان معين داخل المساحة المتاحة، وتوفر تحكمًا دقيقًا في موضع العنصر الفرعي داخل المساحة المتاحة لها.
الخواص الرئيسية (بتعمق):
alignment: (مطلوب) تحدد كيفية محاذاة العنصر الفرعي. تأخذ قيمة من نوعAlignmentGeometry.- يمكن استخدام قيم ثابتة مثل
Alignment.topLeft,Alignment.center,Alignment.bottomRight. - يمكن أيضًا تحديد محاذاة مخصصة باستخدام
Alignment(double x, double y)حيث تتراوح قيمxوyمن -1.0 (البداية) إلى 1.0 (النهاية). على سبيل المثال،Alignment(0.0, 0.0)هوAlignment.center.
- يمكن استخدام قيم ثابتة مثل
widthFactor: إذا لم يكنnull، فإن عرض الـAlignسيكونwidthFactorمضروبًا في عرض العنصر الفرعي. هذا يعني أنAlignستأخذ فقط المساحة التي تحتاجها لعرض العنصر الفرعي مع عامل التحجيم هذا. إذا كانnull، فإنAlignستحاول أن تكون كبيرة بقدر الإمكان.heightFactor: إذا لم يكنnull، فإن ارتفاع الـAlignسيكونheightFactorمضروبًا في ارتفاع العنصر الفرعي. نفس سلوكwidthFactor.child: العنصر الفرعي الذي سيتم محاذاته.
سلوك التخطيط (Layout Behavior)
- القيود (Constraints): تتلقى
Alignقيودًا من الأصل. إذا لم يتم تحديدwidthFactorأوheightFactor، فإنAlignستحاول أن تكون كبيرة بقدر الإمكان ضمن هذه القيود. ثم تمررAlignهذه القيود إلى عنصرها الفرعي. - تحديد الحجم: بعد أن يحدد العنصر الفرعي حجمه، تقوم
Alignبتحديد حجمها الخاص. إذا تم تحديدwidthFactorو/أوheightFactor، فإن حجمAlignسيعتمد على حجم العنصر الفرعي وعامل التحجيم. وإلا، فإنها ستأخذ أكبر حجم ممكن ضمن قيود الأصل. - المحاذاة: تقوم
Alignبوضع العنصر الفرعي داخل مساحتها الخاصة وفقًا لقيمةalignmentالمحددة.
حالات الاستخدام (Use Cases)
- وضع عنصر في زاوية: وضع أيقونة أو زر في زاوية معينة من حاوية.
- توسيط عنصر داخل مساحة غير منتظمة: إذا كان لديك ويدجت أصل لا يوفر توسيطًا مباشرًا، يمكن استخدام
Align. - إنشاء تأثيرات تراكب دقيقة: عند الحاجة إلى وضع عنصر بدقة فوق عنصر آخر في
Stack، يمكن استخدامAlignداخلPositioned.
نصائح للأداء
- استخدم
constمعAlignعندما تكون خصائصها ثابتة لتمكين Flutter من تحسين الأداء. - فهم
widthFactorوheightFactor: استخدم هذه الخصائص بحكمة لتجنب السلوكيات غير المتوقعة في التخطيط، خاصة إذا كنت تتعامل مع قيود مرنة.
مثال:
import 'package:flutter/material.dart';
class AlignExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Align Example')),
body: Center(
child: Container(
height: 200,
width: 200,
color: Colors.blueGrey[100],
child: Align(
alignment: Alignment.bottomRight,
child: Container(
height: 50,
width: 50,
color: Colors.deepOrange,
child: Center(child: Text('BR', style: TextStyle(color: Colors.white))),
),
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: AlignExample()));
}
12. Wrap
الوصف: Wrap هي ويدجت تقوم بترتيب الـ widgets الفرعية في صفوف أو أعمدة، وعندما لا يكون هناك مساحة كافية في الاتجاه الرئيسي، فإنها تقوم بلف (wrap) الـ widgets إلى السطر التالي أو العمود التالي. إنها مفيدة لإنشاء تخطيطات مرنة حيث يمكن أن تتغير عدد العناصر في كل سطر أو عمود، وتعتبر بديلاً ممتازًا لـ Row أو Column عندما يكون هناك احتمال لتجاوز المحتوى.
الخواص الرئيسية (بتعمق):
direction: (افتراضي:Axis.horizontal) الاتجاه الرئيسي الذي يتم فيه ترتيب الـ widgets. يمكن أن يكونAxis.horizontal(من اليسار إلى اليمين أو اليمين إلى اليسار) أوAxis.vertical(من الأعلى إلى الأسفل أو الأسفل إلى الأعلى).alignment: (افتراضي:WrapAlignment.start) تحدد كيفية محاذاة الـ widgets الفرعية على المحور الرئيسي داخل كل "لفة" (run). القيم المتاحة هيstart,end,center,spaceBetween,spaceAround,spaceEvenly.spacing: (افتراضي:0.0) المسافة (بالبكسل) بين الـ widgets على المحور الرئيسي. يتم تطبيق هذه المسافة بين كل عنصر وآخر داخل نفس "اللفة".runAlignment: (افتراضي:WrapAlignment.start) تحدد كيفية محاذاة "اللفات" (runs) على المحور المتقاطع. "اللفة" هي مجموعة من الـ widgets التي تم لفها إلى سطر أو عمود جديد. القيم المتاحة هيstart,end,center,spaceBetween,spaceAround,spaceEvenly.runSpacing: (افتراضي:0.0) المسافة (بالبكسل) بين "اللفات" على المحور المتقاطع. يتم تطبيق هذه المسافة بين كل سطر أو عمود ملفوف وآخر.crossAxisAlignment: (افتراضي:WrapCrossAlignment.start) تحدد كيفية محاذاة الـ widgets الفرعية داخل كل "لفة" على المحور المتقاطع. القيم المتاحة هيstart,end,center.textDirection: يحدد اتجاه النص للعناصر الفرعية. يؤثر على كيفية تفسيرdirectionوalignment.verticalDirection: يحدد كيفية ترتيب العناصر الفرعية على المحور الرئيسي.VerticalDirection.down(افتراضي) يرتبها من الأعلى إلى الأسفل، وVerticalDirection.upيرتبها من الأسفل إلى الأعلى.children: قائمة الـ widgets الفرعية التي سيتم ترتيبها.
سلوك التخطيط (Layout Behavior)
- القيود (Constraints): تتلقى
Wrapقيودًا من الأصل. تقومWrapبتخطيط عناصرها الفرعية واحدًا تلو الآخر على المحور الرئيسي. إذا تجاوز العنصر الفرعي المساحة المتاحة على المحور الرئيسي، فإنWrapتقوم بلفه إلى "لفة" جديدة (سطر أو عمود جديد). - تحديد الحجم: حجم
Wrapيعتمد على حجم عناصرها الفرعية والمسافات المحددة. ستحاولWrapأن تكون كبيرة بما يكفي لاحتواء جميع عناصرها الفرعية مع المسافات البادئة والهوامش. - المرونة في التخطيط: على عكس
RowوColumnالتي قد تسبب تجاوزًا (overflow) إذا كانت عناصرها الفرعية أكبر من المساحة المتاحة، فإنWrapتتعامل مع هذه الحالات بمرونة عن طريق لف العناصر.
حالات الاستخدام (Use Cases)
- الكلمات المفتاحية (Tags) أو الشرائح (Chips): عرض قائمة من الكلمات المفتاحية التي تلتف تلقائيًا إلى السطر التالي عند عدم وجود مساحة كافية.
- معرض الصور المتجاوب: عرض مجموعة من الصور التي تتكيف مع عرض الشاشة.
- أزرار الإجراءات المتعددة: ترتيب مجموعة من الأزرار التي قد تختلف في عددها أو حجمها.
- تخطيطات النماذج الديناميكية: حيث قد تتغير عدد حقول الإدخال أو حجمها.
نصائح للأداء
- استخدم
constمع العناصر الفرعية الثابتة لتمكين Flutter من تحسين الأداء. - فهم
spacingوrunSpacing: استخدم هذه الخصائص للتحكم في التباعد بين العناصر واللفات بدلاً من استخدامPaddingحول كل عنصر، مما يقلل من عدد الـ widgets في شجرة التخطيط. - تجنب عدد كبير جدًا من العناصر: على الرغم من أن
Wrapمرنة، إلا أن وجود عدد كبير جدًا من العناصر قد يؤثر على الأداء، خاصة إذا كانت العناصر معقدة. في هذه الحالات، قد يكونListViewأوGridViewمعWrapكعنصر فرعي أكثر ملاءمة.
مثال:
import 'package:flutter/material.dart';
class WrapExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Wrap Example')),
body: Center(
child: Wrap(
spacing: 8.0, // المسافة الأفقية بين العناصر
runSpacing: 4.0, // المسافة العمودية بين الصفوف
children: List.generate(10, (index) {
return Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text('${index + 1}')),
label: Text('Item ${index + 1}'),
backgroundColor: Colors.blue.shade100,
);
}),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: WrapExample()));
}
الـ Widgets الأخرى لتخطيط المحتوى
13. AspectRatio
الوصف: AspectRatio هي ويدجت تحاول تحديد حجم العنصر الفرعي (child) بنسبة عرض إلى ارتفاع محددة.
مثال:
import 'package:flutter/material.dart';
class AspectRatioExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AspectRatio Example')),
body: Center(
child: Container(
color: Colors.grey[300],
width: 200,
child: AspectRatio(
aspectRatio: 16 / 9, // نسبة العرض إلى الارتفاع
child: Container(
color: Colors.red,
child: Center(child: Text('16:9 Aspect Ratio', style: TextStyle(color: Colors.white))),
),
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: AspectRatioExample()));
}
14. Baseline
الوصف: Baseline هي ويدجت تضع عنصرها الفرعي (child) وفقًا لخط الأساس (baseline) الخاص بالعنصر الفرعي.
مثال:
import 'package:flutter/material.dart';
class BaselineExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Baseline Example')),
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Baseline(
baseline: 50,
baselineType: TextBaseline.alphabetic,
child: Text(
'Hello',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
Baseline(
baseline: 80,
baselineType: TextBaseline.alphabetic,
child: Container(
width: 50,
height: 50,
color: Colors.blue,
),
),
Baseline(
baseline: 30,
baselineType: TextBaseline.alphabetic,
child: Text(
'World',
style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold),
),
),
],
),
),
);
}
}
void main() {
runApp(MaterialApp(home: BaselineExample()));
}
15. ConstrainedBox
الوصف: ConstrainedBox هي ويدجت تفرض قيودًا إضافية على عنصرها الفرعي (child).
مثال:
import 'package:flutter/material.dart';
class ConstrainedBoxExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('ConstrainedBox Example')),
body: Center(
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: 100,
minHeight: 100,
maxWidth: 200,
maxHeight: 200,
),
child: Container(
color: Colors.green,
child: Text('Constrained Box', style: TextStyle(color: Colors.white)),
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: ConstrainedBoxExample()));
}
16. CustomSingleChildLayout
الوصف: CustomSingleChildLayout هي ويدجت تؤجل تخطيط عنصرها الفرعي الوحيد إلى مفوض (delegate) مخصص.
مثال:
import 'package:flutter/material.dart';
class CustomSingleChildLayoutExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('CustomSingleChildLayout Example')),
body: Center(
child: CustomSingleChildLayout(
delegate: MySingleChildLayoutDelegate(),
child: Container(
width: 100,
height: 100,
color: Colors.blue,
child: Center(child: Text('Custom Layout', style: TextStyle(color: Colors.white))),
),
),
),
);
}
}
class MySingleChildLayoutDelegate extends SingleChildLayoutDelegate {
@override
Size getSize(BoxConstraints constraints) {
return Size(constraints.maxWidth, constraints.maxHeight);
}
@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
return BoxConstraints.tightFor(width: constraints.maxWidth / 2, height: constraints.maxHeight / 2);
}
@override
Offset getPositionForChild(Size size, Size childSize) {
return Offset(size.width / 4, size.height / 4);
}
@override
bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate) => false;
}
void main() {
runApp(MaterialApp(home: CustomSingleChildLayoutExample()));
}
17. FittedBox
الوصف: FittedBox تقوم بتحجيم ووضع عنصرها الفرعي (child) داخل نفسها وفقًا لخاصية fit.
مثال:
import 'package:flutter/material.dart';
class FittedBoxExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('FittedBox Example')),
body: Center(
child: Container(
width: 200,
height: 100,
color: Colors.grey[300],
child: FittedBox(
fit: BoxFit.contain,
child: Text(
'This is a long text that needs to fit.',
style: TextStyle(fontSize: 30),
),
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: FittedBoxExample()));
}
18. FractionallySizedBox
الوصف: FractionallySizedBox هي ويدجت تحدد حجم عنصرها الفرعي (child) ككسر من إجمالي المساحة المتاحة.
مثال:
import 'package:flutter/material.dart';
class FractionallySizedBoxExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('FractionallySizedBox Example')),
body: Center(
child: Container(
width: 300,
height: 300,
color: Colors.grey[300],
child: FractionallySizedBox(
widthFactor: 0.5, // 50% من عرض الأصل
heightFactor: 0.75, // 75% من ارتفاع الأصل
child: Container(
color: Colors.orange,
child: Center(child: Text('Fractional Size', style: TextStyle(color: Colors.white))),
),
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: FractionallySizedBoxExample()));
}
19. IntrinsicHeight
الوصف: IntrinsicHeight هي ويدجت تحدد ارتفاع عنصرها الفرعي (child) بناءً على الارتفاع الجوهري (intrinsic height) للعنصر الفرعي.
مثال:
import 'package:flutter/material.dart';
class IntrinsicHeightExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('IntrinsicHeight Example')),
body: Center(
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
color: Colors.red,
width: 50,
child: Center(child: Text('Short', style: TextStyle(color: Colors.white))),
),
Container(
color: Colors.green,
width: 50,
child: Center(child: Text('Very Long Text That Wraps', style: TextStyle(color: Colors.white))),
),
Container(
color: Colors.blue,
width: 50,
child: Center(child: Text('Medium', style: TextStyle(color: Colors.white))),
),
],
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: IntrinsicHeightExample()));
}
20. IntrinsicWidth
الوصف: IntrinsicWidth هي ويدجت تحدد عرض عنصرها الفرعي (child) بناءً على العرض الجوهري (intrinsic width) للعنصر الفرعي.
مثال:
import 'package:flutter/material.dart';
class IntrinsicWidthExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('IntrinsicWidth Example')),
body: Center(
child: IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
color: Colors.red,
height: 50,
child: Center(child: Text('Short', style: TextStyle(color: Colors.white))),
),
Container(
color: Colors.green,
height: 50,
child: Center(child: Text('Very Long Text That Needs Space', style: TextStyle(color: Colors.white))),
),
Container(
color: Colors.blue,
height: 50,
child: Center(child: Text('Medium', style: TextStyle(color: Colors.white))),
),
],
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: IntrinsicWidthExample()));
}
21. LimitedBox
الوصف: LimitedBox هي ويدجت تحد من حجمها فقط عندما تكون غير مقيدة (unconstrained). إنها مفيدة لمنع الـ widgets من التوسع بشكل لا نهائي عندما تكون في بيئة غير مقيدة.
مثال:
import 'package:flutter/material.dart';
class LimitedBoxExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('LimitedBox Example')),
body: ListView(
children: <Widget>[
LimitedBox(
maxHeight: 100, // سيتم تطبيق هذا الحد الأقصى فقط إذا كان الأصل غير مقيد
child: Container(
color: Colors.amber,
child: Text('This box will not exceed 100 height if unconstrained.', style: TextStyle(fontSize: 20)),
),
),
Container(
height: 200,
color: Colors.blueGrey,
child: Center(child: Text('Another Container', style: TextStyle(color: Colors.white))),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(home: LimitedBoxExample()));
}
22. Offstage
الوصف: Offstage هي ويدجت تقوم بتخطيط العنصر الفرعي (child) كما لو كان موجودًا في الشجرة، ولكن دون رسم أي شيء، ودون جعل العنصر الفرعي متاحًا للتفاعل (hit testing).
مثال:
import 'package:flutter/material.dart';
class OffstageExample extends StatefulWidget {
@override
_OffstageExampleState createState() => _OffstageExampleState();
}
class _OffstageExampleState extends State<OffstageExample> {
bool _offstage = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Offstage Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Offstage(
offstage: _offstage,
child: Container(
width: 100,
height: 100,
color: Colors.red,
child: Center(child: Text('Hidden', style: TextStyle(color: Colors.white))),
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
setState(() {
_offstage = !_offstage;
});
},
child: Text(_offstage ? 'Show Widget' : 'Hide Widget'),
),
],
),
),
);
}
}
void main() {
runApp(MaterialApp(home: OffstageExample()));
}
23. OverflowBox
الوصف: OverflowBox هي ويدجت تفرض قيودًا مختلفة على عنصرها الفرعي (child) عما تحصل عليه من الأصل (parent)، مما قد يسمح للعنصر الفرعي بالتجاوز (overflow) الأصل.
مثال:
import 'package:flutter/material.dart';
class OverflowBoxExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('OverflowBox Example')),
body: Center(
child: Container(
width: 100,
height: 100,
color: Colors.grey[300],
child: OverflowBox(
minWidth: 0.0,
minHeight: 0.0,
maxWidth: 200.0,
maxHeight: 200.0,
child: Container(
color: Colors.blue,
width: 150,
height: 150,
child: Center(child: Text('Overflowing Box', style: TextStyle(color: Colors.white))),
),
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: OverflowBoxExample()));
}
24. SizedOverflowBox
الوصف: SizedOverflowBox هي ويدجت ذات حجم محدد ولكنها تمرر قيودها الأصلية إلى عنصرها الفرعي (child)، والذي قد يتجاوز (overflow) حدودها.
مثال:
import 'package:flutter/material.dart';
class SizedOverflowBoxExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SizedOverflowBox Example')),
body: Center(
child: Container(
color: Colors.grey[300],
width: 100,
height: 100,
child: SizedOverflowBox(
size: Size(50, 50), // حجم الـ SizedOverflowBox نفسها
child: Container(
color: Colors.red,
width: 150, // هذا سيتجاوز حجم الـ SizedOverflowBox
height: 150,
child: Center(child: Text('Overflow', style: TextStyle(color: Colors.white))),
),
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: SizedOverflowBoxExample()));
}
25. Transform
الوصف: Transform هي ويدجت تطبق تحويلاً (transformation) قبل رسم عنصرها الفرعي (child). يمكن استخدامها للتدوير، التحجيم، والإزاحة. (تم شرحها سابقًا في Painting widgets، ولكن يتم تضمينها هنا لأهميتها في التخطيط).
مثال:
import 'package:flutter/material.dart';
import 'dart:math' as math;
class TransformLayoutExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Transform Layout Example')),
body: Center(
child: Container(
color: Colors.grey[300],
width: 200,
height: 200,
child: Transform.rotate(
angle: math.pi / 4, // يدور 45 درجة
child: Container(
width: 100,
height: 100,
color: Colors.purple,
child: Center(child: Text('Rotated', style: TextStyle(color: Colors.white))),
),
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: TransformLayoutExample()));
}
26. CustomMultiChildLayout
الوصف: CustomMultiChildLayout هي ويدجت تستخدم مفوضًا (delegate) لتحديد حجم وموضع العديد من العناصر الفرعية.
مثال:
import 'package:flutter/material.dart';
class CustomMultiChildLayoutExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('CustomMultiChildLayout Example')),
body: Center(
child: CustomMultiChildLayout(
delegate: MyMultiChildLayoutDelegate(),
children: <Widget>[
LayoutId(
id: 1,
child: Container(color: Colors.red, width: 50, height: 50),
),
LayoutId(
id: 2,
child: Container(color: Colors.green, width: 70, height: 70),
),
],
),
),
);
}
}
class MyMultiChildLayoutDelegate extends MultiChildLayoutDelegate {
@override
void performLayout(Size size) {
if (hasChild(1)) {
layoutChild(1, BoxConstraints.tightFor(width: 50, height: 50));
positionChild(1, Offset(0, 0));
}
if (hasChild(2)) {
layoutChild(2, BoxConstraints.tightFor(width: 70, height: 70));
positionChild(2, Offset(size.width - 70, size.height - 70));
}
}
@override
bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) => false;
}
void main() {
runApp(MaterialApp(home: CustomMultiChildLayoutExample()));
}
27. CarouselView
الوصف: CarouselView هي ويدجت Material carousel تعرض قائمة قابلة للتمرير من العناصر، يمكن لكل منها تغيير حجمه ديناميكيًا بناءً على التخطيط المختار. (ملاحظة: CarouselView ليست ويدجت أساسية في Flutter SDK، وقد تشير إلى حزم خارجية أو أمثلة. سأقدم مثالًا عامًا لمفهوم الكاروسيل باستخدام PageView).
مثال (باستخدام PageView كمفهوم مشابه):
import 'package:flutter/material.dart';
class CarouselViewExample extends StatelessWidget {
final List<Color> colors = [Colors.red, Colors.green, Colors.blue, Colors.purple];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('CarouselView (PageView) Example')),
body: Center(
child: SizedBox(
height: 200,
child: PageView.builder(
itemCount: colors.length,
itemBuilder: (context, index) {
return Container(
margin: EdgeInsets.all(8.0),
color: colors[index],
child: Center(
child: Text(
'Page ${index + 1}',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
);
},
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: CarouselViewExample()));
}
28. Flow
الوصف: Flow هي ويدجت تنفذ خوارزمية تخطيط التدفق (flow layout algorithm). إنها فعالة للغاية ولكنها تتطلب فهمًا عميقًا لكيفية عمل التخطيطات.
مثال:
import 'package:flutter/material.dart';
class FlowExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flow Example')),
body: Flow(
delegate: MyFlowDelegate(margin: EdgeInsets.all(10.0)),
children: List.generate(5, (index) {
return Container(
width: 80,
height: 80,
color: Colors.primaries[index % Colors.primaries.length],
child: Center(child: Text('${index + 1}', style: TextStyle(color: Colors.white, fontSize: 20))),
);
}),
),
);
}
}
class MyFlowDelegate extends FlowDelegate {
final EdgeInsets margin;
MyFlowDelegate({required this.margin});
@override
void paintChildren(FlowPaintingContext context) {
double x = margin.left;
double y = margin.top;
for (int i = 0; i < context.childCount; i++) {
var w = context.getChildSize(i)!.width + margin.right + margin.left;
if (x + w > context.size.width) {
x = margin.left;
y += context.getChildSize(i)!.height + margin.top + margin.bottom;
}
context.paintChild(i, transform: Matrix4.translationValues(x, y, 0.0));
x += w;
}
}
@override
bool shouldRepaint(covariant FlowDelegate oldDelegate) => false;
}
void main() {
runApp(MaterialApp(home: FlowExample()));
}
29. IndexedStack
الوصف: IndexedStack هي Stack تعرض عنصرًا فرعيًا واحدًا فقط من قائمة العناصر الفرعية الخاصة بها بناءً على فهرس (index) محدد.
مثال:
import 'package:flutter/material.dart';
class IndexedStackExample extends StatefulWidget {
@override
_IndexedStackExampleState createState() => _IndexedStackExampleState();
}
class _IndexedStackExampleState extends State<IndexedStackExample> {
int _currentIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('IndexedStack Example')),
body: Column(
children: <Widget>[
Expanded(
child: IndexedStack(
index: _currentIndex,
children: <Widget>[
Container(color: Colors.red, child: Center(child: Text('Page 1', style: TextStyle(color: Colors.white, fontSize: 30)))),
Container(color: Colors.green, child: Center(child: Text('Page 2', style: TextStyle(color: Colors.white, fontSize: 30)))),
Container(color: Colors.blue, child: Center(child: Text('Page 3', style: TextStyle(color: Colors.white, fontSize: 30)))),
],
),
),
BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.business), label: 'Business'),
BottomNavigationBarItem(icon: Icon(Icons.school), label: 'School'),
],
),
],
),
);
}
}
void main() {
runApp(MaterialApp(home: IndexedStackExample()));
}
30. LayoutBuilder
الوصف: LayoutBuilder هي ويدجت تبني شجرة ويدجت يمكن أن تعتمد على حجم الويدجت الأصلية (parent widget).
مثال:
import 'package:flutter/material.dart';
class LayoutBuilderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('LayoutBuilder Example')),
body: Center(
child: Container(
width: 300,
height: 300,
color: Colors.grey[300],
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
if (constraints.maxWidth > 200) {
return Container(
color: Colors.blue,
child: Center(child: Text('Wide Layout', style: TextStyle(color: Colors.white, fontSize: 20))),
);
} else {
return Container(
color: Colors.red,
child: Center(child: Text('Narrow Layout', style: TextStyle(color: Colors.white, fontSize: 20))),
);
}
},
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: LayoutBuilderExample()));
}
31. ListBody
الوصف: ListBody هي ويدجت ترتب عناصرها الفرعية بالتسلسل على طول محور معين، مما يجبرها على أن تكون بنفس بُعد الأصل على المحور الآخر.
مثال:
import 'package:flutter/material.dart';
class ListBodyExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('ListBody Example')),
body: Center(
child: Container(
width: 200,
height: 300,
color: Colors.grey[300],
child: ListBody(
mainAxis: Axis.vertical,
children: <Widget>[
Container(color: Colors.red, height: 50, child: Center(child: Text('Item 1', style: TextStyle(color: Colors.white)))),
Container(color: Colors.green, height: 50, child: Center(child: Text('Item 2', style: TextStyle(color: Colors.white)))),
Container(color: Colors.blue, height: 50, child: Center(child: Text('Item 3', style: TextStyle(color: Colors.white)))),
],
),
),
),
);
}
}
void main() {
runApp(MaterialApp(home: ListBodyExample()));
}
32. Table
الوصف: Table تعرض الـ widgets الفرعية في صفوف وأعمدة.
مثال:
import 'package:flutter/material.dart';
class TableExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Table Example')),
body: Center(
child: Table(
border: TableBorder.all(),
children: const <TableRow>[
TableRow(
children: <Widget>[
Center(child: Text('Cell 1')),
Center(child: Text('Cell 2')),
Center(child: Text('Cell 3')),
],
),
TableRow(
children: <Widget>[
Center(child: Text('Cell 4')),
Center(child: Text('Cell 5')),
Center(child: Text('Cell 6')),
],
),
],
),
),
);
}
}
void main() {
runApp(MaterialApp(home: TableExample()));
}
Sliver Widgets
الـ Sliver widgets هي ويدجت خاصة تُستخدم لإنشاء تأثيرات تمرير مخصصة وفعالة. يتم استخدامها عادةً داخل CustomScrollView.
33. CupertinoSliverNavigationBar
الوصف: شريط تنقل بتصميم iOS 11 مع عناوين كبيرة باستخدام الـ slivers.
مثال:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class CupertinoSliverNavigationBarExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: CustomScrollView(
slivers: <Widget>[
CupertinoSliverNavigationBar(
largeTitle: Text('My App'),
middle: Text('Details'),
),
SliverFillRemaining(
child: Center(
child: Text('Content Below Navigation Bar'),
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(home: CupertinoSliverNavigationBarExample()));
}
34. CupertinoSliverRefreshControl
الوصف: ويدجت sliver تنفذ التحكم في تحديث المحتوى بالسحب للأسفل على غرار iOS.
مثال:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class CupertinoSliverRefreshControlExample extends StatelessWidget {
Future<void> _handleRefresh() {
return Future.delayed(Duration(seconds: 2)); // Simulate network call
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: CustomScrollView(
slivers: <Widget>[
CupertinoSliverRefreshControl(
onRefresh: _handleRefresh,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
height: 60,
color: Colors.blue[100 * (index % 9)],
child: Center(child: Text('Item $index')),
);
},
childCount: 20,
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(home: CupertinoSliverRefreshControlExample()));
}
35. CustomScrollView
الوصف: CustomScrollView هي ScrollView تنشئ تأثيرات تمرير مخصصة باستخدام الـ slivers.
مثال:
import 'package:flutter/material.dart';
class CustomScrollViewExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('CustomScrollView Example')),
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
expandedHeight: 150.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('Custom Scroll View'),
background: Image.network(
'https://picsum.photos/id/1084/400/200',
fit: BoxFit.cover,
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
height: 80,
color: Colors.amber[100 * (index % 9)],
child: Center(child: Text('Item $index')),
);
},
childCount: 20,
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(home: CustomScrollViewExample()));
}
36. SliverAppBar
الوصف: شريط تطبيق (app bar) بتصميم Material يتكامل مع CustomScrollView.
مثال: (مضمن في مثال CustomScrollView أعلاه)
37. SliverChildBuilderDelegate
الوصف: مفوض (delegate) يوفر عناصر فرعية للـ slivers باستخدام دالة بناء (builder callback).
مثال: (مضمن في أمثلة CupertinoSliverRefreshControl و CustomScrollView)
38. SliverChildListDelegate
الوصف: مفوض (delegate) يوفر عناصر فرعية للـ slivers باستخدام قائمة صريحة.
مثال:
import 'package:flutter/material.dart';
class SliverChildListDelegateExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SliverChildListDelegate Example')),
body: CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate(
<Widget>[
Container(color: Colors.red, height: 100, child: Center(child: Text('Item 1', style: TextStyle(color: Colors.white)))),
Container(color: Colors.green, height: 100, child: Center(child: Text('Item 2', style: TextStyle(color: Colors.white)))),
Container(color: Colors.blue, height: 100, child: Center(child: Text('Item 3', style: TextStyle(color: Colors.white)))),
],
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(home: SliverChildListDelegateExample()));
}
39. SliverFillRemaining
الوصف: sliver يحتوي على عنصر صندوقي واحد يملأ المساحة المتبقية في منفذ العرض (viewport).
مثال: (مضمن في مثال CupertinoSliverNavigationBar)
40. SliverFixedExtentList
الوصف: sliver يضع العديد من العناصر الصندوقية الفرعية بنفس الامتداد على المحور الرئيسي في قائمة خطية.
مثال:
import 'package:flutter/material.dart';
class SliverFixedExtentListExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SliverFixedExtentList Example')),
body: CustomScrollView(
slivers: <Widget>[
SliverFixedExtentList(
itemExtent: 50.0, // ارتفاع ثابت لكل عنصر
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: Text('Item $index'),
);
},
childCount: 20,
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(home: SliverFixedExtentListExample()));
}
41. SliverGrid
الوصف: sliver يضع العديد من العناصر الصندوقية الفرعية في ترتيب ثنائي الأبعاد.
مثال:
import 'package:flutter/material.dart';
class SliverGridExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SliverGrid Example')),
body: CustomScrollView(
slivers: <Widget>[
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 1.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.orange[100 * (index % 9)],
child: Text('Grid Item $index'),
);
},
childCount: 30,
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(home: SliverGridExample()));
}
42. SliverList
الوصف: sliver يضع العديد من العناصر الصندوقية الفرعية في قائمة خطية على طول المحور الرئيسي.
مثال: (مضمن في أمثلة CupertinoSliverRefreshControl و CustomScrollView)
43. SliverPadding
الوصف: sliver يطبق مسافة بادئة (padding) على كل جانب من sliver آخر.
مثال:
import 'package:flutter/material.dart';
class SliverPaddingExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SliverPadding Example')),
body: CustomScrollView(
slivers: <Widget>[
SliverPadding(
padding: const EdgeInsets.all(20.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
height: 50,
color: Colors.purple[100 * (index % 9)],
child: Center(child: Text('Padded Item $index')),
);
},
childCount: 10,
),
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(home: SliverPaddingExample()));
}
44. SliverPersistentHeader
الوصف: sliver يتغير حجمه عندما يتم تمرير الـ sliver إلى حافة منفذ العرض (viewport) المقابلة لـ GrowthDirection الخاص بالـ sliver.
مثال:
import 'package:flutter/material.dart';
class SliverPersistentHeaderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SliverPersistentHeader Example')),
body: CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
delegate: MySliverPersistentHeaderDelegate(
minHeight: 60.0,
maxHeight: 200.0,
child: Container(
color: Colors.lightGreen,
child: Center(child: Text('Persistent Header', style: TextStyle(color: Colors.white, fontSize: 24))),
),
),
pinned: true, // يجعل الرأس ثابتًا عند التمرير
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
height: 80,
color: Colors.grey[100 * (index % 9)],
child: Center(child: Text('List Item $index')),
);
},
childCount: 30,
),
),
],
),
);
}
}
class MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
MySliverPersistentHeaderDelegate({
required this.minHeight,
required this.maxHeight,
required this.child,
});
final double minHeight;
final double maxHeight;
final Widget child;
@override
double get minExtent => minHeight;
@override
double get maxExtent => math.max(maxHeight, minHeight);
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return SizedBox.expand(child: child);
}
@override
bool shouldRebuild(MySliverPersistentHeaderDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}
void main() {
runApp(MaterialApp(home: SliverPersistentHeaderExample()));
}
45. SliverToBoxAdapter
الوصف: sliver يحتوي على ويدجت صندوقية واحدة.
مثال:
import 'package:flutter/material.dart';
class SliverToBoxAdapterExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SliverToBoxAdapter Example')),
body: CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(
child: Container(
height: 150,
color: Colors.pink,
child: Center(child: Text('Regular Box Widget in a Sliver', style: TextStyle(color: Colors.white, fontSize: 20))),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
height: 60,
color: Colors.blueGrey[100 * (index % 9)],
child: Center(child: Text('List Item $index')),
);
},
childCount: 10,
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(home: SliverToBoxAdapterExample()));
}
46. SliverLayoutBuilder
الوصف: SliverLayoutBuilder هي ويدجت تبني شجرة ويدجت يمكن أن تعتمد على قيود الـ sliver الأصلية. إنها مفيدة لإنشاء slivers مخصصة تتفاعل مع بيئة التمرير.
مثال:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class SliverLayoutBuilderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SliverLayoutBuilder Example')),
body: CustomScrollView(
slivers: <Widget>[
SliverLayoutBuilder(
builder: (BuildContext context, SliverConstraints constraints) {
return SliverToBoxAdapter(
child: Container(
height: 100,
color: Colors.deepOrangeAccent,
child: Center(
child: Text(
'Sliver Constraints: ${constraints.viewportMainAxisExtent.toStringAsFixed(1)}',
style: TextStyle(color: Colors.white, fontSize: 18),
),
),
),
);
},
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
height: 60,
color: Colors.indigo[100 * (index % 9)],
child: Center(child: Text('List Item $index')),
);
},
childCount: 10,
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(home: SliverLayoutBuilderExample()));
}
قسم خاص: التخطيط المتجاوب (Responsive Layout) وأفضل الممارسات
بناء واجهات مستخدم تتكيف بسلاسة مع مختلف أحجام الشاشات والاتجاهات (عمودي/أفقي) هو جزء أساسي من تطوير التطبيقات الحديثة. يوفر Flutter مجموعة قوية من الأدوات والتقنيات لتحقيق التخطيط المتجاوب بفعالية.
1. فهم أنواع الأجهزة وأحجام الشاشات
قبل البدء في بناء التخطيط المتجاوب، من المهم فهم الفئات المختلفة للأجهزة:
- الهواتف المحمولة (Mobile): شاشات صغيرة، عادة ما تكون بعرض أقل من 600 بكسل منطقي (logical pixels).
- الأجهزة اللوحية (Tablets): شاشات متوسطة، يتراوح عرضها بين 600 و 1200 بكسل منطقي.
- أجهزة سطح المكتب (Desktop): شاشات كبيرة، بعرض يزيد عن 1200 بكسل منطقي.
2. الأدوات الأساسية للتخطيط المتجاوب
-
MediaQuery: هي الأداة الأساسية للحصول على معلومات حول الشاشة الحالية، مثل:MediaQuery.of(context).size: حجم الشاشة (العرض والارتفاع).MediaQuery.of(context).orientation: اتجاه الشاشة (Orientation.portraitأوOrientation.landscape).MediaQuery.of(context).padding: المساحات التي يشغلها نظام التشغيل (مثل شريط الحالة أو الـ notch).
-
LayoutBuilder: هي ويدجت قوية تقوم ببناء شجرة الـ widgets الخاصة بها بناءً على القيود (constraints) التي تتلقاها من الأصل. هذا يسمح لك بتغيير التخطيط بناءً على المساحة المتاحة للـ widget نفسها، وليس فقط حجم الشاشة بالكامل. -
OrientationBuilder: ويدجت متخصصة تقوم بإعادة بناء واجهة المستخدم عندما يتغير اتجاه الشاشة.
3. أفضل الممارسات والاستراتيجيات
أ. استخدام الـ Widgets المرنة
ExpandedوFlexible: استخدمهما دائمًا داخلRowوColumnلتوزيع المساحة بشكل مرن بدلاً من استخدام أحجام ثابتة.Wrap: استخدمها بدلاً منRowأوColumnعندما يكون هناك احتمال لتجاوز المحتوى، مثل عرض قائمة من الكلمات المفتاحية.FittedBox: تقوم بتحجيم وتحديد موضع عنصرها الفرعي داخل نفسها لتناسب المساحة المتاحة.
ب. التخطيطات التكيفية (Adaptive Layouts)
لا تكتفِ فقط بتغيير حجم العناصر، بل قم بتغيير التخطيط نفسه بناءً على حجم الشاشة. على سبيل المثال:
- في الهواتف: قد تعرض قائمة (
ListView) من العناصر. - في الأجهزة اللوحية: قد تعرض شبكة (
GridView) من نفس العناصر، أو تخطيطًا من جزأين (master-detail layout).
يمكن تحقيق ذلك باستخدام LayoutBuilder للتحقق من العرض المتاح وتحديد الـ widget المناسبة.
ج. استخدام الوحدات النسبية بدلاً من المطلقة
- تجنب الأحجام الثابتة (Hardcoded Sizes): بدلاً من تحديد
width: 200، حاول استخدام نسب من عرض الشاشة، على سبيل المثالwidth: MediaQuery.of(context).size.width * 0.5. FractionallySizedBox: ويدجت مفيدة لتحديد حجم عنصر فرعي كنسبة مئوية من المساحة المتاحة.
4. مثال عملي: تخطيط متجاوب
لنفترض أننا نريد عرض قائمة على الهواتف، وشبكة على الأجهزة اللوحية.
import 'package:flutter/material.dart';
class ResponsiveLayoutExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Responsive Layout')),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
if (constraints.maxWidth > 600) {
// عرض الشبكة للأجهزة اللوحية
return _buildGridView();
} else {
// عرض القائمة للهواتف
return _buildListView();
}
},
),
);
}
Widget _buildListView() {
return ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return Card(
child: ListTile(
leading: Icon(Icons.person),
title: Text('Item $index'),
subtitle: Text('This is a list item'),
),
);
},
);
}
Widget _buildGridView() {
return GridView.count(
crossAxisCount: 4,
children: List.generate(20, (index) {
return Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.person, size: 40),
SizedBox(height: 8),
Text('Item $index'),
],
),
);
}),
);
}
}
void main() {
runApp(MaterialApp(home: ResponsiveLayoutExample()));
}
في هذا المثال، يستخدم LayoutBuilder للتحقق من maxWidth. إذا كان العرض أكبر من 600 بكسل، فإنه يعرض GridView؛ وإلا، فإنه يعرض ListView. هذا يضمن تجربة مستخدم مثالية على كلا النوعين من الأجهزة.