شجرة الـ Widgets: Stateless و Stateful — أساسيات Flutter
Widgets في Flutter
كل شيء Widget!
في Flutter، كل عنصر على الشاشة هو Widget:
- النصوص
- الأزرار
- التخطيطات
- حتى التطبيق نفسه!
أنواع Widgets
1. StatelessWidget
- لا تتغير بعد البناء
- للعناصر الثابتة
- أداء أفضل
2. StatefulWidget
- تتغير خلال التشغيل
- تستخدم setState()
- للعناصر التفاعلية
Widgets شائعة
تخطيط (Layout)
- Container: صندوق مع تنسيق
- Row: ترتيب أفقي
- Column: ترتيب عمودي
- Stack: عناصر فوق بعض
- ListView: قائمة قابلة للتمرير
إدخال (Input)
- TextField: حقل نصي
- Button: أزرار متنوعة
- Checkbox, Switch, Slider
عرض (Display)
- Text: نص
- Image: صورة
- Icon: أيقونة
- Card: بطاقة
القاعدة الذهبية
> إذا كان العنصر يتغير، استخدم StatefulWidget
> إذا كان ثابتاً، استخدم StatelessWidget
// مثال شامل على Widgets
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'تطبيق Widgets',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // primarySwatch لا أثر لها عملياً مع Material 3
useMaterial3: true,
),
home: HomePage(),
);
}
}
// ===== 1. StatelessWidget - بطاقة منتج =====
class ProductCard extends StatelessWidget {
final String name;
final double price;
final String imageUrl;
final VoidCallback? onTap;
const ProductCard({
super.key,
required this.name,
required this.price,
required this.imageUrl,
this.onTap,
});
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// الصورة
ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
child: Image.network(
imageUrl,
height: 120,
width: double.infinity,
fit: BoxFit.cover,
),
),
// المحتوى
Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'$${price.toStringAsFixed(2)}',
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
),
);
}
}
// ===== 2. StatefulWidget - عداد مع أنيميشن =====
class AnimatedCounter extends StatefulWidget {
const AnimatedCounter({super.key});
@override
State<AnimatedCounter> createState() => _AnimatedCounterState();
}
class _AnimatedCounterState extends State<AnimatedCounter>
with SingleTickerProviderStateMixin {
int _count = 0;
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_animation = Tween<double>(begin: 1, end: 1.2).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _increment() {
setState(() => _count++);
_controller.forward().then((_) => _controller.reverse());
}
void _decrement() {
if (_count > 0) {
setState(() => _count--);
_controller.forward().then((_) => _controller.reverse());
}
}
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
const Text('العداد', style: TextStyle(fontSize: 18)),
const SizedBox(height: 16),
ScaleTransition(
scale: _animation,
child: Text(
'$_count',
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _decrement,
child: const Icon(Icons.remove),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: _increment,
child: const Icon(Icons.add),
),
],
),
],
),
),
);
}
}
// ===== الصفحة الرئيسية =====
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('تعلم Widgets'),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'StatefulWidget - عداد',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const AnimatedCounter(),
const SizedBox(height: 24),
const Text(
'StatelessWidget - منتجات',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 0.8,
children: [
ProductCard(
name: 'هاتف ذكي',
price: 599.99,
imageUrl: 'https://via.placeholder.com/150',
onTap: () => print('تم النقر!'),
),
ProductCard(
name: 'سماعات',
price: 199.99,
imageUrl: 'https://via.placeholder.com/150',
),
],
),
],
),
),
);
}
}اختبر فهمك