الشبكات في Flutter: GridView لعرض المنتجات والصور
معرض الصور في هاتفك شبكة. منتجات المتجر شبكة. أيقونات التطبيقات على الشاشة الرئيسية شبكة. عندما تريد عرض عناصر كثيرة في صفوف وأعمدة معاً، فالأداة هي 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
Section titled “حجم الخانة: childAspectRatio”لاحظ أن الصناديق في المثال السابق ليس لها عرض ولا ارتفاع، ومع ذلك ظهرت مربعة. السبب أن الشبكة هي التي تحدد حجم الخانة وتتجاهل الحجم الذي يطلبه الابن:
- العرض = عرض الشبكة مقسوماً على عدد الأعمدة (بعد خصم المسافات).
- الارتفاع يُحسب من
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 |
عناصر كثيرة أو قادمة من بيانات |
شبكة داخل صفحة تتمرر
Section titled “شبكة داخل صفحة تتمرر”أحياناً تريد شبكة صغيرة في وسط صفحة داخل 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 تبني كل العناصر مرة واحدة.
المشروع: شبكة منتجات
Section titled “المشروع: شبكة منتجات”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تعطي عمودين على الهاتف، وأعمدة أكثر على الشاشات الأعرض.
جرّب بنفسك
Section titled “جرّب بنفسك”- شغّل شبكة المنتجات، ثم كبّر النافذة وصغّرها وراقب عدد الأعمدة.
- غيّر
childAspectRatioإلى 0.7 ثم إلى 1.5 ولاحظ شكل البطاقات. - أضف أربعة منتجات جديدة إلى القائمة.
- تحدٍّ: اجعل الضغط على المنتج يفتح شاشة تفاصيل تعرض اسمه وسعره. (تلميح:
InkWellأوGestureDetectorمعNavigator.push.)
أخطاء شائعة
Section titled “أخطاء شائعة”- محاولة تحديد حجم الخانة من الابن: كتابة
widthوheightللابن لا تؤثر. الحجم يتحدد من عدد الأعمدة وchildAspectRatio. - GridView داخل Column بدون Expanded: نفس خطأ «unbounded height» الذي عرفته مع
ListView. - استخدام GridView.count لمئات العناصر: تُبنى كلها مرة واحدة. استخدم
GridView.builder.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ماذا تعني childAspectRatio: 0.5؟
النسبة هي العرض مقسوماً على الارتفاع. إذا كانت 0.5 فالعرض نصف الارتفاع، أي أن الخانة طويلة.
سؤال
تريد شبكة يزيد عدد أعمدتها تلقائياً على الشاشات العريضة. أي مفوَّض تستخدم؟
هذا المفوَّض يأخذ أقصى عرض للخانة، ويحسب عدد الأعمدة المناسب لعرض الشاشة تلقائياً.
الخلاصة
Section titled “الخلاصة”GridView.countبعدد أعمدة ثابت، وGridView.builderللشبكات الطويلة.- الشبكة هي التي تحدد حجم الخانة، و
childAspectRatioتتحكم في نسبة عرضها إلى ارتفاعها. maxCrossAxisExtentتصنع شبكة متجاوبة بدون شروط.shrinkWrapمعNeverScrollableScrollPhysicsلشبكة صغيرة داخل صفحة تتمرر.- داخل الخانة، استخدم
ExpandedوmaxLinesلتتجنب Overflow.