ConstrainedBox و FittedBox و AspectRatio
فهم قيود التخطيط
نظام التخطيط في Flutter مبني على مفهوم القيود. كل عنصر واجهة يتلقى قيودًا من العنصر الأب (الحد الأدنى والأقصى للعرض/الارتفاع) ويجب عليه اختيار حجم ضمن تلك الحدود. في هذا الدرس، نستكشف العناصر التي تتيح لك التحكم وتجاوز وتكييف القيود لبناء تخطيطات دقيقة ومتجاوبة.
ConstrainedBox
يفرض ConstrainedBox قيودًا إضافية على العنصر الفرعي. يستخدم كائن BoxConstraints الذي يحدد minWidth وmaxWidth وminHeight وmaxHeight. القيود النهائية المطبقة على العنصر الفرعي هي تقاطع قيود الأب والقيود التي تحددها أنت.
استخدام ConstrainedBox الأساسي
ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 100,
maxWidth: 300,
minHeight: 50,
maxHeight: 200,
),
child: Container(
color: Colors.blue,
child: const Text('أنا مُقيّد!'),
),
)
مُنشئات المصنع الشائعة لـ BoxConstraints تُبسّط الأنماط الشائعة:
مُنشئات مصنع BoxConstraints
// فرض حجم محدد
BoxConstraints.tight(const Size(200, 100))
// السماح بأي حجم حتى الأبعاد المعطاة
BoxConstraints.loose(const Size(300, 200))
// التوسع لملء كل المساحة المتاحة
const BoxConstraints.expand()
// عرض ثابت، ارتفاع مرن
const BoxConstraints.tightFor(width: 250)
// تقييد محور واحد فقط
const BoxConstraints(
maxWidth: 400,
// minWidth الافتراضي 0.0
// الارتفاع غير مقيد
)
ConstrainedBox فقط تشديد القيود—لا يمكنه جعلها أكثر مرونة. إذا كان الأب يقول بالفعل “يجب أن يكون العرض 100 بالضبط”، فإن ConstrainedBox بـ maxWidth: 300 لن يكون له أي تأثير.
UnconstrainedBox
يزيل UnconstrainedBox قيود الأب بالكامل، مما يسمح للعنصر الفرعي بالعرض بحجمه الطبيعي (الجوهري). هذا مفيد عندما يجبر الأب عنصرًا على التوسع لكنك تريد أن يكون الفرعي أصغر. كن حذرًا—إذا تجاوز الفرعي الحدود، سيعرض Flutter تحذير تجاوز.
مثال UnconstrainedBox
// داخل Row يمد العناصر الفرعية
Row(
children: [
Expanded(
child: UnconstrainedBox(
child: Container(
width: 80,
height: 40,
color: Colors.green,
child: const Center(
child: Text('حر!'),
),
),
),
),
],
)
LimitedBox
يحد LimitedBox حجم العنصر الفرعي فقط عندما تكون القيود الواردة غير محدودة. هذا مفيد بشكل خاص داخل العروض القابلة للتمرير مثل ListView حيث يكون الارتفاع غير مقيد.
LimitedBox في ListView
ListView(
children: [
LimitedBox(
maxHeight: 200,
child: Container(
color: Colors.orange,
child: const Center(
child: Text('أقصى ارتفاع 200 بكسل في القائمة'),
),
),
),
],
)
OverflowBox
يمرر OverflowBox قيودًا مختلفة لعنصره الفرعي عن تلك التي تلقاها من عنصره الأب. على عكس ConstrainedBox، يمكنه فعلاً تخفيف القيود. قد يرسم العنصر الفرعي خارج حدود OverflowBox.
مثال OverflowBox
SizedBox(
width: 100,
height: 100,
child: OverflowBox(
maxWidth: 200,
maxHeight: 200,
child: Container(
width: 200,
height: 200,
color: Colors.red.withOpacity(0.5),
child: const Center(
child: Text('أنا أتجاوز!'),
),
),
),
)
FittedBox
يقوم FittedBox بتحجيم وتموضع عنصره الفرعي داخله وفقًا لوضع BoxFit. وهو مفيد بشكل خاص لتحجيم النصوص والصور أو أشجار العناصر بأكملها لتناسب مساحة معينة.
FittedBox مع أوضاع BoxFit
// تحجيم النص لملء العرض
SizedBox(
width: 300,
height: 50,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
'هذا النص يتقلص إذا كان عريضًا جدًا',
style: const TextStyle(fontSize: 40),
),
),
)
// قيم BoxFit الشائعة:
// BoxFit.contain - التحجيم للملاءمة مع الحفاظ على نسبة العرض إلى الارتفاع
// BoxFit.cover - التحجيم للملء مع الحفاظ على النسبة وقد يُقص
// BoxFit.fill - التمدد للملء وقد يُشوّه
// BoxFit.fitWidth - تحجيم العرض للملاءمة وقد يتجاوز الارتفاع
// BoxFit.fitHeight - تحجيم الارتفاع للملاءمة وقد يتجاوز العرض
// BoxFit.scaleDown - مثل contain لكن يُصغّر فقط
// BoxFit.none - بدون تحجيم مع توسيط العنصر الفرعي
FittedBox أن يكون لعنصره الفرعي حجم محدود. إذا حاول الفرعي أن يكون عريضًا بلا حدود (مثل Row غير مقيد)، سيطرح Flutter خطأ. تأكد دائمًا من أن العنصر الفرعي له أبعاد محدودة.
مثال عملي: صورة متجاوبة
FittedBox للصور المتجاوبة
class ResponsiveImage extends StatelessWidget {
final String imageUrl;
const ResponsiveImage({super.key, required this.imageUrl});
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 600,
maxHeight: 400,
),
child: FittedBox(
fit: BoxFit.contain,
child: Image.network(imageUrl),
),
);
}
}
AspectRatio
يقوم عنصر AspectRatio بتحجيم عنصره الفرعي وفقًا لنسبة عرض إلى ارتفاع محددة. يحاول أولاً مطابقة أكبر عرض تسمح به القيود، ثم يحدد الارتفاع وفقًا للنسبة. إذا وفّر الأب قيودًا ضيقة، قد لا تكون نسبة العرض إلى الارتفاع قابلة للتحقيق.
عنصر AspectRatio
// حاوية مشغل فيديو 16:9
AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color: Colors.black,
child: const Center(
child: Icon(
Icons.play_circle_outline,
color: Colors.white,
size: 64,
),
),
),
)
// حاوية مربعة
AspectRatio(
aspectRatio: 1.0,
child: Container(
decoration: BoxDecoration(
color: Colors.purple,
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text(
'1:1',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
),
)
IntrinsicHeight و IntrinsicWidth
يقوم IntrinsicHeight بتحجيم عنصره الفرعي وفقًا لارتفاعه الجوهري، وIntrinsicWidth يفعل نفس الشيء للعرض. هذه مفيدة عندما تحتاج أن تتطابق العناصر المتجاورة في الحجم لكن نظام التخطيط لا يفعل ذلك تلقائيًا.
IntrinsicHeight لبطاقات متساوية الارتفاع
IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: Card(
color: Colors.blue[50],
child: const Padding(
padding: EdgeInsets.all(16),
child: Text('نص قصير'),
),
),
),
Expanded(
child: Card(
color: Colors.green[50],
child: const Padding(
padding: EdgeInsets.all(16),
child: Text(
'هذه البطاقة تحتوي على محتوى أكثر بكثير '
'مما يجعلها أطول. البطاقة الأخرى ستتمدد '
'لتطابق هذا الارتفاع.',
),
),
),
),
],
),
)
IntrinsicHeight وIntrinsicWidth بإجراء تمرير تخطيط استكشافي، يقيس العنصر الفرعي مرتين. استخدمهما باعتدال—يمكن أن يسببا أداء O(n²) عند التداخل. فضّل استراتيجيات تخطيط أخرى (مثل Table أو CrossAxisAlignment.stretch) عندما يكون ذلك ممكنًا.
مثال عملي: حقل إدخال مُقيّد
حقل نص مُقيّد
class ConstrainedInputField extends StatelessWidget {
const ConstrainedInputField({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 200,
maxWidth: 500,
),
child: TextField(
decoration: InputDecoration(
labelText: 'عنوان البريد الإلكتروني',
hintText: 'أدخل بريدك الإلكتروني',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
prefixIcon: const Icon(Icons.email),
),
),
),
);
}
}
مثال عملي: مشغل فيديو بنسبة عرض إلى ارتفاع
مشغل فيديو مع AspectRatio
class VideoPlayerCard extends StatelessWidget {
final String title;
final String thumbnailUrl;
const VideoPlayerCard({
super.key,
required this.title,
required this.thumbnailUrl,
});
@override
Widget build(BuildContext context) {
return Card(
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: Stack(
fit: StackFit.expand,
children: [
Image.network(
thumbnailUrl,
fit: BoxFit.cover,
),
Container(
color: Colors.black26,
child: const Center(
child: Icon(
Icons.play_circle_fill,
color: Colors.white,
size: 56,
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(12),
child: Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
],
),
);
}
}
ConstrainedBox لإضافة حدود دنيا/قصوى، وFittedBox لتحجيم المحتوى في مساحة، وAspectRatio للحفاظ على التحجيم النسبي، وIntrinsicHeight/Width فقط عندما لا يحقق أي عنصر تخطيط آخر النتيجة المطلوبة. معًا، تمنحك هذه العناصر تحكمًا دقيقًا في كيفية تحجيم العناصر ضمن نظام التخطيط القائم على القيود في Flutter.