تخطَّ إلى المحتوى

الشبكات في Flutter: GridView لعرض المنتجات والصور

الدرس 16 من 33 · Flutter

معرض الصور في هاتفك شبكة. منتجات المتجر شبكة. أيقونات التطبيقات على الشاشة الرئيسية شبكة. عندما تريد عرض عناصر كثيرة في صفوف وأعمدة معاً، فالأداة هي GridView. في هذا الدرس ستتعلم أنواعها الثلاثة، وكيف تجعل الشبكة تتكيف مع حجم الشاشة.

GridView.count: عدد أعمدة ثابت

Section titled “GridView.count: عدد أعمدة ثابت”

أبسط شكل: تحدد عدد الأعمدة، والشبكة تحسب الباقي.

GridView.count(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
padding: const EdgeInsets.all(8),
children: [
Container(color: Colors.red.shade200),
Container(color: Colors.green.shade200),
Container(color: Colors.blue.shade200),
Container(color: Colors.amber.shade200),
Container(color: Colors.purple.shade200),
Container(color: Colors.teal.shade200),
],
)
  • crossAxisCount: عدد الأعمدة. هنا 3، فتترتب العناصر الستة في صفين.
  • mainAxisSpacing: المسافة بين الصفوف.
  • crossAxisSpacing: المسافة بين الأعمدة.

الشبكة تتمرر رأسياً تلقائياً إذا زادت الصفوف على ارتفاع الشاشة. وكما في Row و Column: المحور الرئيسي (main) هو اتجاه التمرير، والمحور العرضي (cross) عمودي عليه.

تخيّل الشبكة مثل علبة بيض: عدد الخانات في الصف ثابت، وكلما زاد البيض زادت الصفوف.

لاحظ أن الصناديق في المثال السابق ليس لها عرض ولا ارتفاع، ومع ذلك ظهرت مربعة. السبب أن الشبكة هي التي تحدد حجم الخانة وتتجاهل الحجم الذي يطلبه الابن:

  • العرض = عرض الشبكة مقسوماً على عدد الأعمدة (بعد خصم المسافات).
  • الارتفاع يُحسب من childAspectRatio، وهي نسبة العرض إلى الارتفاع، وقيمتها الافتراضية 1 أي مربع.
GridView.count(
crossAxisCount: 2,
childAspectRatio: 3 / 4,
children: [
Container(color: Colors.indigo.shade200),
Container(color: Colors.amber.shade200),
],
)

3 / 4 تعني أن العرض 3 أجزاء والارتفاع 4، فتصبح الخانة أطول من عرضها، وهذا مناسب لبطاقة منتج فيها صورة وتحتها اسم وسعر. وقيمة مثل 2 تجعل الخانة عريضة قصيرة.

GridView.builder: للشبكات الطويلة

Section titled “GridView.builder: للشبكات الطويلة”

مثل ListView.builder تماماً: تبني الخانات الظاهرة فقط. استخدمها دائماً مع البيانات الحقيقية.

GridView.builder(
padding: const EdgeInsets.all(8),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
),
itemCount: 100,
itemBuilder: (context, index) {
return Container(
color: Colors.indigo.shade100,
alignment: Alignment.center,
child: Text('عنصر ${index + 1}'),
);
},
)

الجديد هنا gridDelegate: كائن يحمل إعدادات تقسيم الشبكة. اسمه طويل ومخيف، لكن معناه بسيط: «مفوَّض شبكة بعدد أعمدة ثابت». وخصائصه هي نفسها التي عرفتها: crossAxisCount و mainAxisSpacing و crossAxisSpacing و childAspectRatio.

شبكة متجاوبة: maxCrossAxisExtent

Section titled “شبكة متجاوبة: maxCrossAxisExtent”

عمودان مناسبان للهاتف، لكنهما على تابلت يصنعان بطاقتين عملاقتين. بدلاً من تحديد عدد الأعمدة، حدّد أقصى عرض للخانة، ودع الشبكة تحسب العدد:

GridView.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 180,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
),
itemCount: 20,
itemBuilder: (context, index) {
return Container(color: Colors.teal.shade100);
},
)

maxCrossAxisExtent: 180 تعني: «لا تجعل الخانة أعرض من 180». على هاتف عرضه 360 تحصل على عمودين، وعلى تابلت عرضه 800 تحصل على خمسة أعمدة، بدون أي شرط في الكود. ويوجد اختصار للشبكات القصيرة: GridView.extent(maxCrossAxisExtent: 180, children: [...]).

النوع متى تستخدمه
GridView.count عناصر قليلة وعدد أعمدة ثابت
GridView.extent عناصر قليلة وشبكة متجاوبة
GridView.builder عناصر كثيرة أو قادمة من بيانات

أحياناً تريد شبكة صغيرة في وسط صفحة داخل SingleChildScrollView. عندها يتصارع تمريران، وتطلب الشبكة ارتفاعاً غير محدود. الحل خاصيتان:

GridView.count(
crossAxisCount: 4,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: const [
Icon(Icons.phone_android),
Icon(Icons.laptop),
Icon(Icons.headphones),
Icon(Icons.watch),
],
)
  • shrinkWrap: true: تجعل الشبكة تأخذ ارتفاع محتواها فقط.
  • physics: const NeverScrollableScrollPhysics(): توقف تمرير الشبكة نفسها، فتتمرر الصفحة الأم فقط.

استخدم هذه الحيلة مع الشبكات الصغيرة فقط، لأن shrinkWrap تبني كل العناصر مرة واحدة.

lib/products_grid.dart
class Product {
const Product(this.name, this.price, this.icon);
final String name;
final int price;
final IconData icon;
}
const products = [
Product('سماعة', 450, Icons.headphones),
Product('ساعة ذكية', 1200, Icons.watch),
Product('لوحة مفاتيح', 380, Icons.keyboard),
Product('فأرة', 150, Icons.mouse),
Product('كاميرا', 2500, Icons.camera_alt),
Product('مكبر صوت', 600, Icons.speaker),
];
class ProductsGrid extends StatelessWidget {
const ProductsGrid({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('المنتجات')),
body: GridView.builder(
padding: const EdgeInsets.all(12),
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 220,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.9,
),
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.indigo.shade50,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
Expanded(
child: Icon(product.icon, size: 56, color: Colors.indigo),
),
Text(
product.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text('${product.price} جنيهاً'),
],
),
);
},
),
);
}
}

أهم ما في المشروع:

  • البيانات في قائمة من كائنات Product، والشبكة تقرأ منها بالرقم index.
  • الأيقونة داخل Expanded، فتأخذ كل الارتفاع الباقي بعد الاسم والسعر. بهذا لا يحدث Overflow مهما تغيّر حجم الخانة.
  • maxCrossAxisExtent: 220 تعطي عمودين على الهاتف، وأعمدة أكثر على الشاشات الأعرض.
  1. شغّل شبكة المنتجات، ثم كبّر النافذة وصغّرها وراقب عدد الأعمدة.
  2. غيّر childAspectRatio إلى 0.7 ثم إلى 1.5 ولاحظ شكل البطاقات.
  3. أضف أربعة منتجات جديدة إلى القائمة.
  4. تحدٍّ: اجعل الضغط على المنتج يفتح شاشة تفاصيل تعرض اسمه وسعره. (تلميح: InkWell أو GestureDetector مع Navigator.push.)
  • محاولة تحديد حجم الخانة من الابن: كتابة width و height للابن لا تؤثر. الحجم يتحدد من عدد الأعمدة و childAspectRatio.
  • GridView داخل Column بدون Expanded: نفس خطأ «unbounded height» الذي عرفته مع ListView.
  • استخدام GridView.count لمئات العناصر: تُبنى كلها مرة واحدة. استخدم GridView.builder.

سؤال

ماذا تعني childAspectRatio: 0.5؟

سؤال

تريد شبكة يزيد عدد أعمدتها تلقائياً على الشاشات العريضة. أي مفوَّض تستخدم؟

  • GridView.count بعدد أعمدة ثابت، و GridView.builder للشبكات الطويلة.
  • الشبكة هي التي تحدد حجم الخانة، و childAspectRatio تتحكم في نسبة عرضها إلى ارتفاعها.
  • maxCrossAxisExtent تصنع شبكة متجاوبة بدون شروط.
  • shrinkWrap مع NeverScrollableScrollPhysics لشبكة صغيرة داخل صفحة تتمرر.
  • داخل الخانة، استخدم Expanded و maxLines لتتجنب Overflow.