SliverAppBar و SliverList و SliverGrid
مقدمة إلى Slivers
Slivers هي اللبنات الأساسية للمناطق القابلة للتمرير في Flutter. بينما تُعد عناصر مثل ListView و GridView مريحة، إلا أنها في الحقيقة مبنية فوق slivers. عندما تحتاج إلى تأثيرات تمرير متقدمة — رؤوس قابلة للطي، عناصر ثابتة، أو تخطيطات تمرير مختلطة — فإنك تعمل مباشرة مع slivers داخل CustomScrollView.
CustomScrollView يجمع عدة slivers في سطح تمرير موحد.
أساسيات CustomScrollView
عنصر CustomScrollView يأخذ قائمة من slivers في خاصية slivers. كل عنصر فرعي يجب أن يكون عنصر sliver (يبدأ بـ Sliver). لا يمكنك خلط العناصر العادية والـ slivers مباشرة.
هيكل CustomScrollView الأساسي
class MyScrollPage extends StatelessWidget {
const MyScrollPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
// عناصر Sliver تُوضع هنا
const SliverAppBar(
title: Text('تطبيقي'),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(
title: Text('عنصر \$index'),
),
childCount: 50,
),
),
],
),
);
}
}
SliverAppBar
SliverAppBar هو أحد أقوى عناصر sliver. ينشئ شريط تطبيق يمكنه التوسع والطي والعوم والتثبيت والالتقاط أثناء تمرير المستخدم. يحل محل AppBar القياسي عند استخدامه داخل CustomScrollView.
الخصائص الرئيسية
- expandedHeight — ارتفاع شريط التطبيق عند التوسع الكامل.
- floating — إذا كان
true، يظهر شريط التطبيق بمجرد أن يمرر المستخدم للأعلى، حتى لو لم يصل إلى القمة. - pinned — إذا كان
true، يبقى شريط التطبيق المطوي مرئيًا في الأعلى. - snap — إذا كان
true(يتطلبfloating: true)، ينفتح شريط التطبيق أو ينغلق بالكامل بدلاً من الظهور الجزئي. - flexibleSpace — عنصر يتمدد لملء المنطقة الموسعة، عادةً
FlexibleSpaceBar.
SliverAppBar مع FlexibleSpaceBar
class CollapsingHeaderPage extends StatelessWidget {
const CollapsingHeaderPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 250.0,
floating: false,
pinned: true,
snap: false,
flexibleSpace: FlexibleSpaceBar(
title: const Text('منظر الجبل'),
centerTitle: true,
background: Image.network(
'https://picsum.photos/800/400',
fit: BoxFit.cover,
),
collapseMode: CollapseMode.parallax,
),
actions: [
IconButton(
icon: const Icon(Icons.share),
onPressed: () {},
),
],
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(
leading: CircleAvatar(child: Text('\${index + 1}')),
title: Text('عنصر \${index + 1}'),
subtitle: const Text('نص فرعي'),
),
childCount: 30,
),
),
],
),
);
}
}
CollapseMode.parallax لتأثير التمرير المتوازي على صورة الخلفية، CollapseMode.pin لإبقاء الخلفية ثابتة، أو CollapseMode.none لعدم وجود سلوك خاص.
العوم والالتقاط
خصائص floating و snap تعمل معًا للتحكم في كيفية ظهور شريط التطبيق عند التمرير للأعلى في منتصف القائمة.
SliverAppBar عائم مع التقاط
SliverAppBar(
expandedHeight: 200.0,
floating: true,
pinned: false,
snap: true,
flexibleSpace: FlexibleSpaceBar(
title: const Text('شريط الوصول السريع'),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.deepPurple, Colors.indigo],
),
),
),
),
)
snap: true يتطلب floating: true. إذا عيّنت snap: true بدون floating: true، سيطرح Flutter خطأ تأكيد في وقت التشغيل.
SliverList
SliverList يعرض قائمة خطية من العناصر الفرعية داخل CustomScrollView. يوفر Flutter 3.x مُنشئات مريحة تبسّط الإنشاء.
SliverList.builder
المُنشئ الأكثر شيوعًا، مكافئ لـ ListView.builder. يبني العناصر الفرعية كسولًا عند الطلب.
مثال SliverList.builder
final List<String> cities = [
'لندن', 'باريس', 'طوكيو', 'نيويورك',
'سيدني', 'دبي', 'برلين', 'روما',
];
SliverList.builder(
itemCount: cities.length,
itemBuilder: (context, index) {
return Card(
margin: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
child: ListTile(
leading: const Icon(Icons.location_city),
title: Text(cities[index]),
trailing: const Icon(Icons.chevron_right),
onTap: () {
// الانتقال إلى تفاصيل المدينة
},
),
);
},
)
SliverList.separated
هذا المُنشئ يضيف عنصر فاصل بين كل عنصر، تمامًا مثل ListView.separated.
مثال SliverList.separated
SliverList.separated(
itemCount: 20,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: Row(
children: [
CircleAvatar(
radius: 24,
backgroundColor: Colors.primaries[index % Colors.primaries.length],
child: Text(
'\${index + 1}',
style: const TextStyle(color: Colors.white),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'جهة اتصال \${index + 1}',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text(
'contact\${index + 1}@example.com',
style: TextStyle(color: Colors.grey[600]),
),
],
),
),
],
),
);
},
separatorBuilder: (context, index) => const Divider(
height: 1,
indent: 72,
),
)
SliverGrid
SliverGrid يرتب العناصر الفرعية في شبكة ثنائية الأبعاد داخل CustomScrollView. تتحكم في تخطيط الشبكة باستخدام gridDelegate.
SliverGrid مع SliverGridDelegateWithFixedCrossAxisCount
SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
childAspectRatio: 1.0,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return Card(
color: Colors.primaries[index % Colors.primaries.length],
child: Center(
child: Text(
'بلاطة \${index + 1}',
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
);
},
childCount: 20,
),
)
SliverGridDelegateWithMaxCrossAxisExtent عندما تريد أن يكون لكل بلاطة عرض أقصى وتدع Flutter يحسب عدد الأعمدة تلقائيًا. هذا رائع لتخطيطات الشبكة المتجاوبة.
SliverFixedExtentList
عندما يكون لجميع العناصر في القائمة نفس الارتفاع الثابت، فإن SliverFixedExtentList أكثر كفاءة من SliverList لأن Flutter يمكنه حساب مواقع العناصر دون قياس كل عنصر فرعي.
مثال SliverFixedExtentList
SliverFixedExtentList(
itemExtent: 72.0,
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey.shade200,
),
),
),
child: Row(
children: [
Icon(
Icons.music_note,
color: Colors.blue.shade400,
),
const SizedBox(width: 16),
Expanded(
child: Text(
'مقطع \${index + 1} - عنوان الأغنية',
style: const TextStyle(fontSize: 16),
),
),
Text(
'3:\${(index * 7 % 60).toString().padLeft(2, '0')}',
style: TextStyle(color: Colors.grey[600]),
),
],
),
);
},
childCount: 100,
),
)
SliverAnimatedList
SliverAnimatedList يوفر إدراجات وإزالات متحركة داخل CustomScrollView. يعمل مثل AnimatedList لكن كـ sliver.
مثال SliverAnimatedList
class AnimatedSliverListPage extends StatefulWidget {
const AnimatedSliverListPage({super.key});
@override
State<AnimatedSliverListPage> createState() =>
_AnimatedSliverListPageState();
}
class _AnimatedSliverListPageState extends State<AnimatedSliverListPage> {
final GlobalKey<SliverAnimatedListState> _listKey =
GlobalKey<SliverAnimatedListState>();
final List<String> _items = ['تفاح', 'موز', 'كرز'];
void _addItem() {
final index = _items.length;
_items.add('فاكهة \${index + 1}');
_listKey.currentState?.insertItem(
index,
duration: const Duration(milliseconds: 400),
);
}
void _removeItem(int index) {
final removed = _items.removeAt(index);
_listKey.currentState?.removeItem(
index,
(context, animation) => SizeTransition(
sizeFactor: animation,
child: Card(
color: Colors.red.shade100,
child: ListTile(title: Text(removed)),
),
),
duration: const Duration(milliseconds: 300),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
const SliverAppBar(
title: Text('قائمة Sliver متحركة'),
pinned: true,
),
SliverAnimatedList(
key: _listKey,
initialItemCount: _items.length,
itemBuilder: (context, index, animation) {
return SizeTransition(
sizeFactor: animation,
child: Card(
margin: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
child: ListTile(
title: Text(_items[index]),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _removeItem(index),
),
),
),
);
},
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _addItem,
child: const Icon(Icons.add),
),
);
}
}
مثال عملي: تخطيط متجر التطبيقات
لنجمع كل شيء في مثال واقعي — صفحة متجر تطبيقات مع رأس قابل للطي وقسم مميز أفقي وشبكة تطبيقات.
تخطيط متجر التطبيقات الكامل
class AppStorePage extends StatelessWidget {
const AppStorePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
// رأس قابل للطي
SliverAppBar(
expandedHeight: 200,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: const Text('متجر التطبيقات'),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.blue, Colors.indigo],
),
),
child: const Center(
child: Icon(
Icons.store,
size: 64,
color: Colors.white70,
),
),
),
),
),
// عنوان القسم
const SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
'أفضل التطبيقات',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
),
),
// شبكة التطبيقات
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 16),
sliver: SliverGrid(
gridDelegate:
const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.75,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleAvatar(
radius: 30,
backgroundColor: Colors
.primaries[index % Colors.primaries.length],
child: const Icon(
Icons.apps,
color: Colors.white,
),
),
const SizedBox(height: 8),
Text(
'تطبيق \${index + 1}',
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
5,
(i) => Icon(
Icons.star,
size: 14,
color: i < 4
? Colors.amber
: Colors.grey.shade300,
),
),
),
],
),
);
},
childCount: 12,
),
),
),
// مسافة سفلية
const SliverToBoxAdapter(
child: SizedBox(height: 32),
),
],
),
);
}
}
SliverAppBar يوفر رؤوس قابلة للطي مع مساحة مرنة. SliverList و SliverGrid يتعاملان مع التخطيطات الخطية والشبكية داخل CustomScrollView. استخدم SliverFixedExtentList للأداء عندما تتشارك العناصر نفس الارتفاع، و SliverAnimatedList للإضافات والإزالات المتحركة. اجمع عدة slivers في CustomScrollView واحد لواجهات تمرير غنية وعالية الأداء.