GridView وتخطيطات الشبكة
مقدمة عن GridView
عنصر GridView يعرض العناصر في شبكة ثنائية الأبعاد. هو في الأساس قائمة قابلة للتمرير من العناصر مرتبة في صفوف وأعمدة. يوفر Flutter عدة منشئات محسّنة لحالات استخدام مختلفة، من الشبكات البسيطة ذات الأعمدة الثابتة إلى التخطيطات الديناميكية التي تتكيف مع حجم الشاشة.
GridView.count
منشئ GridView.count ينشئ شبكة بعدد ثابت من الأعمدة (البلاطات لكل صف). هذه أبسط طريقة لإنشاء شبكة عندما تعرف بالضبط عدد الأعمدة التي تريدها.
مثال GridView.count
GridView.count(
crossAxisCount: 3, // 3 أعمدة
mainAxisSpacing: 8.0, // المسافة العمودية بين العناصر
crossAxisSpacing: 8.0, // المسافة الأفقية بين العناصر
padding: const EdgeInsets.all(16.0),
children: List.generate(12, (index) {
return Container(
decoration: BoxDecoration(
color: Colors.blue.shade100,
borderRadius: BorderRadius.circular(8.0),
),
child: Center(
child: Text(
'عنصر \${index + 1}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
);
}),
)
GridView.count جميع العناصر مسبقاً. للشبكات الكبيرة، استخدم GridView.builder بدلاً من ذلك.
GridView.extent
منشئ GridView.extent ينشئ شبكة حيث يكون لكل بلاطة حد أقصى للمدى العرضي. يحسب Flutter عدد الأعمدة تلقائياً بناءً على العرض المتاح والحد الأقصى للمدى الذي تحدده. هذا يجعلها متجاوبة بطبيعتها.
مثال GridView.extent
// كل بلاطة بعرض أقصى 150 بكسل
// على شاشة 400 بكسل: عمودان (200 بكسل لكل منهما)
// على شاشة 600 بكسل: 4 أعمدة (150 بكسل لكل منهما)
GridView.extent(
maxCrossAxisExtent: 150.0,
mainAxisSpacing: 12.0,
crossAxisSpacing: 12.0,
padding: const EdgeInsets.all(16.0),
children: categories.map((category) {
return Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(category.icon, size: 32.0, color: Colors.blue),
const SizedBox(height: 8.0),
Text(
category.name,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 12.0),
),
],
),
);
}).toList(),
)
GridView.extent عندما تريد شبكة متجاوبة تضبط عدد الأعمدة تلقائياً بناءً على المساحة المتاحة. هذا مفيد بشكل خاص للشبكات التي يجب أن تبدو جيدة على الهواتف والأجهزة اللوحية.
GridView.builder
منشئ GridView.builder ينشئ عناصر الشبكة بشكل كسول، يبني فقط المرئية على الشاشة. هذا هو النهج الموصى به للشبكات الكبيرة أو المحملة ديناميكياً. يتطلب SliverGridDelegate للتحكم في تخطيط الشبكة.
GridView.builder مع SliverGridDelegateWithFixedCrossAxisCount
// شبكة كتالوج المنتجات
GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 16.0,
crossAxisSpacing: 16.0,
childAspectRatio: 0.75, // نسبة العرض / الارتفاع
),
padding: const EdgeInsets.all(16.0),
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return Card(
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Image.network(
product.imageUrl,
width: double.infinity,
fit: BoxFit.cover,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
style: const TextStyle(fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4.0),
Text(
'\$\${product.price.toStringAsFixed(2)}',
style: TextStyle(
color: Colors.green.shade700,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
);
},
)
خيارات SliverGridDelegate
مفوّض الشبكة يتحكم في كيفية تحجيم وتموضع العناصر. يوفر Flutter مفوّضَين مدمجَين:
مقارنة مفوّض الشبكة
// SliverGridDelegateWithFixedCrossAxisCount
// عدد أعمدة ثابت - العناصر تتمدد لملء المساحة المتاحة
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // بالضبط 3 أعمدة
mainAxisSpacing: 8.0, // الفجوة العمودية
crossAxisSpacing: 8.0, // الفجوة الأفقية
childAspectRatio: 1.0, // بلاطات مربعة (عرض/ارتفاع = 1)
)
// SliverGridDelegateWithMaxCrossAxisExtent
// أعمدة ديناميكية بناءً على أقصى عرض للبلاطة
const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0, // كل بلاطة بعرض أقصى 200 بكسل
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
childAspectRatio: 1.5, // أعرض من طولها
// mainAxisExtent: 120.0, // بديل: حجم محور رئيسي ثابت
)
شرح childAspectRatio بعمق
خاصية childAspectRatio تتحكم في نسبة عرض كل بلاطة إلى ارتفاعها. فهم هذا أمر حاسم لتصميم شبكات ذات مظهر جيد.
أمثلة childAspectRatio
// childAspectRatio = العرض / الارتفاع
// بلاطات مربعة
childAspectRatio: 1.0 // العرض == الارتفاع
// بلاطات أفقية (أعرض من طولها)
childAspectRatio: 16 / 9 // نسبة الشاشة العريضة
childAspectRatio: 2.0 // ضعف العرض مقارنة بالارتفاع
// بلاطات عمودية (أطول من عرضها)
childAspectRatio: 0.75 // بطاقات المنتجات
childAspectRatio: 0.5 // بطاقات طويلة جداً
childAspectRatio: 2 / 3 // نسبة بورتريه كلاسيكية
// مثال عملي: شبكة منتجات متجاوبة
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.7, // بطاقات طويلة لصور المنتجات + المعلومات
mainAxisSpacing: 12.0,
crossAxisSpacing: 12.0,
),
itemCount: 20,
itemBuilder: (context, index) => const ProductCard(),
)
childAspectRatio تحكماً كافياً، استخدم mainAxisExtent على المفوّض لتحديد ارتفاع بكسل دقيق لكل بلاطة بدلاً من ذلك.
مثال عملي: معرض الصور
معرض الصور
class PhotoGalleryScreen extends StatelessWidget {
final List<String> photoUrls;
const PhotoGalleryScreen({super.key, required this.photoUrls});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('المعرض')),
body: LayoutBuilder(
builder: (context, constraints) {
// متجاوب: أعمدة أكثر على الشاشات الأوسع
final crossAxisCount = constraints.maxWidth > 600 ? 4 : 3;
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
),
padding: const EdgeInsets.all(4.0),
itemCount: photoUrls.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
// فتح عارض الصور بالشاشة الكاملة
},
child: Hero(
tag: 'photo_\$index',
child: Image.network(
photoUrls[index],
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Container(
color: Colors.grey.shade200,
child: const Center(
child: CircularProgressIndicator(),
),
);
},
),
),
);
},
);
},
),
);
}
}
مثال عملي: لوحة معلومات
شبكة لوحة المعلومات
class DashboardScreen extends StatelessWidget {
const DashboardScreen({super.key});
@override
Widget build(BuildContext context) {
final tiles = [
DashboardTile(
icon: Icons.shopping_cart,
title: 'الطلبات',
value: '156',
color: Colors.blue,
),
DashboardTile(
icon: Icons.people,
title: 'المستخدمون',
value: '2,340',
color: Colors.green,
),
DashboardTile(
icon: Icons.attach_money,
title: 'الإيرادات',
value: '\$12.5K',
color: Colors.orange,
),
DashboardTile(
icon: Icons.trending_up,
title: 'النمو',
value: '+23%',
color: Colors.purple,
),
];
return GridView.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
mainAxisSpacing: 16.0,
crossAxisSpacing: 16.0,
childAspectRatio: 1.2,
),
padding: const EdgeInsets.all(16.0),
itemCount: tiles.length,
itemBuilder: (context, index) {
final tile = tiles[index];
return Card(
elevation: 2.0,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(tile.icon, size: 36.0, color: tile.color),
const SizedBox(height: 8.0),
Text(
tile.value,
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
color: tile.color,
),
),
const SizedBox(height: 4.0),
Text(
tile.title,
style: const TextStyle(color: Colors.grey),
),
],
),
),
);
},
);
}
}
GridView.builder و LayoutBuilder لإنشاء شبكات متجاوبة حقاً. استخدم العرض المتاح من قيود LayoutBuilder لضبط عدد الأعمدة أو أقصى مدى للبلاطات ديناميكياً.