أساسيات Row و Column
مقدمة إلى Row و Column
في Flutter، يُعدّ Row و Column أهم عنصرين أساسيين في التخطيط. يرتبان العناصر الفرعية في خط أفقي (Row) أو خط عمودي (Column). تعتمد تقريباً كل شاشة تبنيها في Flutter على مجموعات من هذين العنصرين.
كلاهما يمتد من عنصر Flex في الخلفية. Row هو ببساطة Flex مع direction: Axis.horizontal، و Column هو Flex مع direction: Axis.vertical. فهم هذه العلاقة يساعدك على استيعاب كيفية حساب الأحجام وترتيب العناصر الفرعية.
children التي تأخذ List<Widget>.عنصر Row
يرتب Row عناصره الفرعية أفقياً. يُوضع كل عنصر فرعي بجانب العنصر السابق على المحور الأفقي. يمنح Row كل عنصر فرعي مساحة عمودية بقدر ما يملكه Row نفسه، ويحدد كل عنصر فرعي عرضه الخاص.
مثال أساسي لـ Row
Row(
children: <Widget>[
Icon(Icons.star, color: Colors.amber, size: 32),
Icon(Icons.star, color: Colors.amber, size: 32),
Icon(Icons.star, color: Colors.amber, size: 32),
Text('3 نجوم', style: TextStyle(fontSize: 18)),
],
)
في هذا المثال، تُرتب ثلاث أيقونات نجوم وعنوان نصي جنباً إلى جنب. يحسب Row العرض الإجمالي المطلوب بجمع العرض الذاتي لكل عنصر فرعي.
عنصر Column
يرتب Column عناصره الفرعية عمودياً. يُكدّس كل عنصر فرعي أسفل العنصر السابق. يمنح Column كل عنصر فرعي مساحة أفقية بقدر ما يملكه Column نفسه، ويحدد كل عنصر فرعي ارتفاعه الخاص.
مثال أساسي لـ Column
Column(
children: <Widget>[
Text('العنصر الأول', style: TextStyle(fontSize: 20)),
Text('العنصر الثاني', style: TextStyle(fontSize: 20)),
Text('العنصر الثالث', style: TextStyle(fontSize: 20)),
],
)
خاصية children
يقبل كلا من Row و Column خاصية children، وهي List<Widget>. يمكنك تضمين أي عنصر في هذه القائمة -- Text أو Icon أو Container أو Image أو حتى Row و Column أخرى.
ListView بدلاً من Column، أو ListView مع scrollDirection: Axis.horizontal بدلاً من Row. لا يدعم Row و Column التمرير وسيحدث تجاوز overflow إذا تجاوزت العناصر الفرعية المساحة المتاحة.كيف يستخدم Row و Column عنصر Flex
في الخلفية، كلا من Row و Column هما فئتان فرعيتان من عنصر Flex. الفرق الوحيد هو خاصية direction:
Row و Column كـ Flex
// Row يعادل:
Flex(
direction: Axis.horizontal,
children: [/* ... */],
)
// Column يعادل:
Flex(
direction: Axis.vertical,
children: [/* ... */],
)
هذا يعني أن كل خاصية متاحة في Flex -- مثل mainAxisAlignment و crossAxisAlignment و mainAxisSize -- متاحة أيضاً في Row و Column. المحور الرئيسي هو اتجاه التخطيط (أفقي لـ Row، عمودي لـ Column)، والمحور المتقاطع هو عمودي عليه.
mainAxisSize: Min مقابل Max
تتحكم خاصية mainAxisSize في مقدار المساحة التي يشغلها Row أو Column على محوره الرئيسي:
- MainAxisSize.max (الافتراضي) -- يأخذ العنصر كل المساحة المتاحة على المحور الرئيسي، حتى لو لم تحتاجها عناصره الفرعية.
- MainAxisSize.min -- يتقلص العنصر ليناسب فقط المساحة التي تحتاجها عناصره الفرعية.
مقارنة mainAxisSize
// يأخذ العرض الكامل للعنصر الأب
Row(
mainAxisSize: MainAxisSize.max,
children: [
Text('مرحباً'),
Text('بالعالم'),
],
)
// يتقلص ليناسب عرض المحتوى
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('مرحباً'),
Text('بالعالم'),
],
)
MainAxisSize.min، لن يكون لخصائص المحاذاة مثل MainAxisAlignment.spaceBetween أي تأثير مرئي لأنه لا توجد مساحة إضافية لتوزيعها.تضمين Row داخل Column والعكس
أحد أقوى الأنماط في Flutter هو تضمين عناصر التخطيط. يمكنك وضع Row داخل Column لإنشاء تخطيطات شبيهة بالشبكة، أو تضمين Column داخل Row لأقسام عمودية متجاورة.
Row داخل Column
Column(
children: <Widget>[
Text('الملف الشخصي', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
SizedBox(height: 16),
Row(
children: <Widget>[
CircleAvatar(radius: 30, child: Icon(Icons.person)),
SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('أحمد علي', style: TextStyle(fontSize: 18)),
Text('مطور Flutter', style: TextStyle(color: Colors.grey)),
],
),
],
),
],
)
ينشئ هذا النمط بطاقة ملف شخصي بعنوان في الأعلى، يتبعه صف يحتوي على صورة رمزية وتفاصيل المستخدم مرتبة عمودياً.
خطأ شائع: القيود غير المحدودة
أحد أكثر الأخطاء شيوعاً التي يواجهها المبتدئون هو وضع Row داخل Row آخر، أو Column داخل عنصر قابل للتمرير، بدون تقييد حجم العنصر الفرعي. يسبب هذا خطأ القيود غير المحدودة.
ListView) داخل Row بدون تقييده، لا يستطيع Flutter تحديد عرض العنصر الداخلي ويُطلق خطأ في التخطيط.المشكلة
// خطأ: Row داخل Row بدون قيود
Row(
children: [
Row(
children: [
Text('هذا سيسبب خطأ'),
],
),
],
)
// الحل: غلّف Row الداخلي بـ Expanded أو SizedBox
Row(
children: [
Expanded(
child: Row(
children: [
Text('هذا يعمل بشكل صحيح'),
],
),
),
],
)
تحدث نفس المشكلة عند وضع Column داخل Column الموجود داخل عنصر قابل للتمرير. يحاول Column الداخلي أن يكون بارتفاع لا نهائي. الحل هو تغليف العنصر الداخلي بـ Expanded أو Flexible أو إعطائه حجماً ثابتاً باستخدام SizedBox.
مثال عملي: شريط العنوان
لنبنِ نمط واجهة مستخدم شائع -- شريط عنوان مع زر رجوع وعنوان وأيقونات إجراءات:
تخطيط شريط العنوان
Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
color: Colors.blue,
child: Row(
children: <Widget>[
IconButton(
icon: Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {},
),
Expanded(
child: Text(
'تطبيقي',
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
IconButton(
icon: Icon(Icons.settings, color: Colors.white),
onPressed: () {},
),
],
),
)
زر الرجوع وأيقونة الإعدادات لهما أحجام ثابتة، بينما يستخدم العنوان Expanded لأخذ كل المساحة المتبقية وتوسيط نفسه.
مثال عملي: تخطيط نموذج عمودي
النماذج عمودية بطبيعتها، مما يجعل Column الخيار المثالي:
تخطيط نموذج باستخدام Column
Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('إنشاء حساب', style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold)),
SizedBox(height: 24),
TextField(decoration: InputDecoration(labelText: 'الاسم الكامل', border: OutlineInputBorder())),
SizedBox(height: 16),
TextField(decoration: InputDecoration(labelText: 'البريد الإلكتروني', border: OutlineInputBorder())),
SizedBox(height: 16),
TextField(decoration: InputDecoration(labelText: 'كلمة المرور', border: OutlineInputBorder()), obscureText: true),
SizedBox(height: 24),
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(padding: EdgeInsets.symmetric(vertical: 16)),
child: Text('إنشاء حساب', style: TextStyle(fontSize: 16)),
),
],
),
)
استخدام CrossAxisAlignment.stretch يجعل كل عنصر فرعي يأخذ العرض الكامل لـ Column، وهو مثالي لحقول النماذج والأزرار.
مثال عملي: مجموعة أزرار أفقية
صف من أزرار الإجراءات المتساوية التباعد هو نمط شائع آخر:
مجموعة أزرار باستخدام Row
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Column(
children: [
IconButton(icon: Icon(Icons.call, color: Colors.green), onPressed: () {}),
Text('اتصال'),
],
),
Column(
children: [
IconButton(icon: Icon(Icons.message, color: Colors.blue), onPressed: () {}),
Text('رسالة'),
],
),
Column(
children: [
IconButton(icon: Icon(Icons.email, color: Colors.red), onPressed: () {}),
Text('بريد'),
],
),
],
)
كل زر هو Column يحتوي على أيقونة وعنوان. يوزعها Row بالتساوي عبر العرض المتاح باستخدام MainAxisAlignment.spaceEvenly.
الملخص
- Row يرتب العناصر الفرعية أفقياً؛ Column يرتبها عمودياً.
- كلاهما يمتد من Flex ويتشاركان نفس خصائص المحاذاة والتحجيم.
mainAxisSizeيتحكم في ما إذا كان العنصر يتوسع لملء المساحة المتاحة (max) أو يتقلص ليناسب عناصره الفرعية (min).- تضمين Rows و Columns يمكّن من إنشاء تخطيطات معقدة، لكن احذر من القيود غير المحدودة.
- استخدم
ExpandedأوSizedBoxلحل مشاكل القيود عند تضمين عناصر flex. - Row و Column لا يدعمان التمرير -- استخدم
ListViewللقوائم القابلة للتمرير.