فهم شجرة الودجات
ما هي الودجة؟
في Flutter، الودجة هي وصف غير قابل للتغيير لجزء من واجهة المستخدم. الودجات ليست عناصر واجهة المستخدم الفعلية التي تراها على الشاشة. بدلاً من ذلك، هي كائنات تكوين خفيفة الوزن تصف كيف يجب أن تبدو واجهة المستخدم. عندما يبني Flutter واجهة المستخدم، يقرأ أوصاف الودجات هذه وينشئ العناصر المرئية الفعلية منها.
الودجة كائن تكوين
// الودجة تصف ماذا تعرض، وليس كيف تعرضه.
// الإطار يتعامل مع جزء "كيف".
// ودجة Text هذه مجرد وصف:
// "أريد نصاً يقول Hello بحجم خط 24"
const Text(
'Hello, Flutter!',
style: TextStyle(fontSize: 24),
)
// ودجة Container هذه مجرد وصف:
// "أريد صندوقاً أزرق 200x200 بزوايا مستديرة"
Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(16),
),
)
// الودجات غير قابلة للتغيير - بمجرد إنشائها لا يمكن تغييرها.
// عندما يحتاج شيء للتغيير، يتم إنشاء ودجة جديدة كلياً.
تسلسل شجرة الودجات
الودجات منظمة في هيكل شجري يسمى شجرة الودجات. كل تطبيق Flutter لديه ودجة جذر في الأعلى، وجميع الودجات الأخرى متداخلة داخلها كأبناء. هذا الهيكل التسلسلي هو كيفية تركيب واجهات مستخدم معقدة من لبنات بناء بسيطة.
تصور شجرة الودجات
// تأمل هذه الشاشة البسيطة في Flutter:
MaterialApp
+-- Scaffold
+-- AppBar
| +-- Text('My App')
+-- body: Padding
+-- Column
+-- Text('Welcome!')
+-- SizedBox(height: 16)
+-- ElevatedButton
| +-- Row
| +-- Icon(Icons.login)
| +-- SizedBox(width: 8)
| +-- Text('Sign In')
+-- TextButton
+-- Text('Create Account')
// الكود الذي ينشئ هذه الشجرة:
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('My App'),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Welcome!'),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {},
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.login),
SizedBox(width: 8),
Text('Sign In'),
],
),
),
TextButton(
onPressed: () {},
child: const Text('Create Account'),
),
],
),
),
),
);
علاقات الأب والابن
في شجرة الودجات، كل ودجة (عدا الجذر) لديها أب واحد بالضبط. يمكن أن يكون للودجات صفر أو واحد أو عدة أبناء حسب نوعها. فهم هذه العلاقات هو المفتاح لبناء تخطيطات صحيحة.
أنواع الودجات حسب عدد الأبناء
// صفر أبناء (ودجات ورقية):
// تعرض هذه الودجات محتوى لكن لا تحتوي على ودجات أخرى
const Text('Hello') // تعرض نصاً
const Icon(Icons.star) // تعرض أيقونة
Image.asset('logo.png') // تعرض صورة
const SizedBox(height: 20) // مساحة فارغة
const CircularProgressIndicator() // مؤشر تحميل
// ابن واحد (ودجات ابن واحد):
// تغلف هذه الودجات ودجة ابن واحدة بالضبط
Center(child: Text('Centered'))
Padding(padding: EdgeInsets.all(8), child: Text('Padded'))
Container(color: Colors.red, child: Text('In a box'))
Expanded(child: Text('Flexible'))
GestureDetector(onTap: () {}, child: Text('Tappable'))
Align(alignment: Alignment.topRight, child: Text('Aligned'))
// عدة أبناء (ودجات متعددة الأبناء):
// تأخذ هذه الودجات قائمة من ودجات الأبناء
Column(children: [Text('A'), Text('B'), Text('C')])
Row(children: [Icon(Icons.star), Text('Star')])
Stack(children: [Image(...), Positioned(child: Text(...))])
ListView(children: [ListTile(...), ListTile(...)])
Wrap(children: [Chip(...), Chip(...), Chip(...)])
فهم BuildContext
BuildContext هو مرجع لموقع الودجة في شجرة الودجات. كل دالة build() لودجة تستقبل معامل BuildContext. يسمح هذا السياق للودجة بإيجاد والتفاعل مع الودجات فوقها في الشجرة (أسلافها).
BuildContext عملياً
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
// context يمثل موقع هذه الودجة في الشجرة
// الوصول للسمة الحالية
final theme = Theme.of(context);
// الوصول لأبعاد الشاشة
final screenSize = MediaQuery.of(context).size;
// الوصول للمتنقل للتوجيه
// Navigator.of(context).push(...)
// الوصول لـ scaffold لعرض الشريط السفلي
// ScaffoldMessenger.of(context).showSnackBar(...)
return Container(
color: theme.colorScheme.primary,
width: screenSize.width * 0.8,
child: const Text('Themed Widget'),
);
}
}
كيف يتنقل BuildContext في الشجرة
// شجرة الودجات:
// MaterialApp (يوفر Theme, MediaQuery, Navigator)
// +-- Scaffold (يوفر ScaffoldMessenger)
// +-- AppBar
// +-- body: MyWidget <-- السياق يشير هنا
// +-- Container
// +-- Text
// عندما يستدعي MyWidget الأمر Theme.of(context):
// 1. يبدأ Flutter من موقع MyWidget
// 2. يمشي لأعلى في الشجرة باحثاً عن مزود Theme
// 3. يجده عند مستوى MaterialApp
// 4. يعيد ThemeData
// مهم: السياق يمكنه النظر للأعلى فقط (نحو الأسلاف)
// لا يمكنه النظر للأسفل في الأبناء أو جانبياً للأشقاء
// خطأ شائع - سياق خاطئ:
Scaffold(
body: Builder(
builder: (BuildContext innerContext) {
// innerContext هو داخل Scaffold
// لذا ScaffoldMessenger.of(innerContext) يعمل!
return ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(innerContext).showSnackBar(
const SnackBar(content: Text('Hello!')),
);
},
child: const Text('Show SnackBar'),
);
},
),
);
BuildContext الموجود عند أو فوق الودجة التي تحاول الوصول إليها. مثلاً، استخدام سياق Scaffold نفسه لاستدعاء ScaffoldMessenger.of(context) سيفشل لأن السياق في نفس مستوى Scaffold وليس تحته. استخدم ودجة Builder للحصول على سياق داخل Scaffold.كيف يبني Flutter واجهة المستخدم من الودجات
عندما يحتاج Flutter لعرض واجهة المستخدم، يمر بعملية محددة جيداً. فهم هذه العملية يساعدك في كتابة كود أكثر كفاءة وتصحيح المشكلات أسرع.
عملية البناء
// الخطوة 1: يحدد كودك شجرة الودجات
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Text('Hello'),
),
),
);
}
}
// الخطوة 2: يستدعي Flutter دالة build() على كل ودجة
// بدءاً من الجذر وبأسلوب العمق أولاً:
// 1. MyApp.build() تعيد MaterialApp(...)
// 2. MaterialApp.build() تعيد ودجاتها الداخلية
// 3. Scaffold.build() تعيد ودجاتها الداخلية
// 4. Center.build() تعيد ودجاتها الداخلية
// 5. Text.build() - عقدة ورقية، لا شيء آخر للبناء
// الخطوة 3: يتم إنشاء العناصر (أو إعادة استخدامها)
// كل ودجة تحصل على عنصر مقابل
// العناصر تحتفظ بالحالة القابلة للتغيير وموقع الشجرة
// الخطوة 4: تحسب RenderObjects التخطيط
// القيود تتدفق للأسفل: الأب يخبر الابن بحجمه الأقصى/الأدنى
// الأحجام تتدفق للأعلى: الابن يخبر الأب بحجمه الفعلي
// الخطوة 5: ترسم RenderObjects على اللوحة
// كل كائن عرض يرسم نفسه في موقعه المحسوب
// المُركّب يجمع كل الطبقات في الصورة النهائية
عدم قابلية الودجات للتغيير
جميع فئات الودجات في Flutter مُعلّمة بتعليق @immutable. هذا يعني بمجرد إنشاء كائن ودجة، لا يمكن تغيير أي من خصائصه. إذا احتاجت واجهة المستخدم للتغيير، ينشئ Flutter كائن ودجة جديد كلياً بالقيم الجديدة.
لماذا الودجات غير قابلة للتغيير
// الودجات غير قابلة للتغيير - خصائصها نهائية
class MyButton extends StatelessWidget {
const MyButton({
super.key,
required this.label, // نهائي - لا يمكن تغييره
required this.onPressed, // نهائي - لا يمكن تغييره
});
final String label; // لا يمكن تغييره بعد الإنشاء
final VoidCallback onPressed; // لا يمكن تغييره بعد الإنشاء
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: Text(label),
);
}
}
// عندما يحتاج الملصق للتغيير:
// الودجة القديمة: MyButton(label: 'Save', ...)
// الودجة الجديدة: MyButton(label: 'Saving...', ...)
//
// يقارن Flutter الودجات القديمة والجديدة:
// - نفس النوع (MyButton)؟ نعم
// - نفس المفتاح؟ نعم (أو كلاهما null)
// - خصائص مختلفة؟ نعم (الملصق تغير)
// النتيجة: تحديث العنصر الموجود بالودجة الجديدة
// هذا فعال لأن:
// 1. الودجات رخيصة الإنشاء (مجرد كائنات تكوين)
// 2. يحدّث Flutter فقط ما تغير فعلاً
// 3. لا حاجة لمقارنة DOM يدوية كما في أطر الويب
const على مُنشئات الودجات كلما أمكن. ودجات const تُنشأ في وقت الترجمة وتُعاد استخدامها، مما يعني أن Flutter يمكنه تخطي مقارنتها بالكامل أثناء إعادة البناء، مما يحسن الأداء.createElement() ودورة حياة العنصر
كل ودجة لديها دالة createElement() تنشئ عنصرها المقابل. العنصر هو ما يعيش فعلاً في الشجرة ويدير دورة حياة الودجة. فهم هذه العلاقة يفسر كيف يحدّث Flutter واجهة المستخدم بكفاءة.
علاقة الودجة بالعنصر
// كل ودجة تنشئ عنصراً:
// StatelessWidget > StatelessElement
// StatefulWidget > StatefulElement
// RenderObjectWidget > RenderObjectElement
// دورة حياة العنصر:
// 1. الإنشاء - يُستدعى widget.createElement()
// العنصر يُركّب في الشجرة
// 2. التحديث - الأب يعيد البناء بودجة جديدة
// يُستدعى Element.update(newWidget)
// العنصر يحتفظ بموقعه، يحدّث فقط مرجع الودجة
// 3. إلغاء التنشيط - الودجة تُزال من الشجرة
// يُستدعى Element.deactivate()
// قد يُعاد تنشيط العنصر إذا نُقل
// 4. التخلص - العنصر يُزال بشكل دائم
// يُستدعى Element.unmount()
// يتم تحرير الموارد
// لـ StatefulWidget، كائن State مرتبط بالعنصر:
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int count = 0; // الحالة تعيش في العنصر، وليس الودجة
@override
Widget build(BuildContext context) {
return Text('Count: \$count');
}
}
// عندما يُعاد بناء CounterWidget:
// 1. يُنشأ نسخة CounterWidget جديدة (تكوين غير قابل للتغيير)
// 2. يرى العنصر نفس نوع الودجة (CounterWidget)
// 3. يحتفظ العنصر بكائن State (_CounterWidgetState)
// 4. يُستدعى State.build() بقيمة count المحفوظة
// النتيجة: الحالة تُحفظ عبر إعادة البناء!
خط أنابيب العرض
يحول خط أنابيب العرض في Flutter أوصاف ودجاتك إلى بكسلات فعلية على الشاشة من خلال ثلاث مراحل رئيسية: البناء، التخطيط، والرسم.
مرحلة البناء
تفاصيل مرحلة البناء
// مرحلة البناء تنشئ/تحدّث شجرة الودجات
// يستدعي Flutter دالة build() على الودجات "المتسخة" (تحتاج إعادة بناء)
// تصبح الودجة متسخة عندما:
// 1. يُستدعى setState() (StatefulWidget)
// 2. يتغير InheritedWidget تعتمد عليه
// 3. يعيد أبها البناء ويمرر وسائط مختلفة
// يحسّن Flutter بإعادة بناء الأشجار الفرعية المتسخة فقط:
//
// MaterialApp <-- نظيف، تخطى
// +-- Scaffold <-- نظيف، تخطى
// +-- Column <-- متسخ، أعد بناء هذه الشجرة الفرعية
// +-- Text('A') <-- أُعيد بناؤه (الأب أُعيد بناؤه)
// +-- Counter <-- أُعيد بناؤه (هذا استدعى setState)
// +-- Text('C') <-- أُعيد بناؤه (الأب أُعيد بناؤه)
// تحسين: استخدم ودجات const للأجزاء الثابتة
Column(
children: [
const Text('A'), // const = لا يُعاد بناؤها أبداً
Counter(), // ديناميكية، ستُعاد بناؤها
const Text('C'), // const = لا يُعاد بناؤها أبداً
],
)
مرحلة التخطيط
مرحلة التخطيط - القيود والأحجام
// يستخدم التخطيط خوارزمية مرور واحد:
// 1. القيود تذهب للأسفل (من الأب للابن)
// 2. الأحجام تذهب للأعلى (من الابن للأب)
// 3. المواقع يحددها الأب
// مثال: Column بابنين في شاشة 400x800
//
// الشاشة تمرر لـ Column:
// BoxConstraints(0 <= w <= 400, 0 <= h <= 800)
//
// Column يمرر للابن الأول (Text):
// BoxConstraints(0 <= w <= 400, 0 <= h <= 800)
// Text يقيس نفسه: Size(120, 20)
//
// Column يمرر للابن الثاني (Container):
// BoxConstraints(0 <= w <= 400, 0 <= h <= 780)
// Container يحدد حجمه: Size(400, 200)
//
// Column يحسب حجمه:
// Size(400, 220) // عرض أوسع ابن، مجموع الارتفاعات
//
// Column يحدد مواقع الأبناء:
// Text عند Offset(0, 0)
// Container عند Offset(0, 20)
// فهم القيود يساعد في تصحيح مشكلات التخطيط!
// "الودجة تحصل على قيودها من أبيها"
// "الودجة تقرر حجمها (ضمن القيود)"
// "الودجة تحدد مواقع أبنائها"
مرحلة الرسم
تفاصيل مرحلة الرسم
// بعد التخطيط، كل RenderObject يعرف حجمه وموقعه
// الآن يرسم نفسه على Canvas
// ترتيب الرسم مهم للودجات المتراكبة:
// الودجات ترسم بترتيب الشجرة (العمق أولاً)
// الأبناء اللاحقون يرسمون فوق الأبناء السابقين
// في Stack:
Stack(
children: [
Container(color: Colors.red), // يُرسم أولاً (الخلف)
Container(color: Colors.blue), // يُرسم ثانياً (الوسط)
Container(color: Colors.green), // يُرسم أخيراً (الأمام)
],
)
// بعض الودجات تنشئ طبقات منفصلة للكفاءة:
// - Opacity, Transform, ClipRect تنشئ طبقات جديدة
// - الطبقات يمكن تركيبها بواسطة GPU بشكل مستقل
// - هذا يعني: تغيير الشفافية لا يتطلب إعادة رسم الأبناء
// RepaintBoundary ينشئ حدود رسم:
RepaintBoundary(
child: ComplexAnimatedWidget(), // يُعاد رسمها بشكل مستقل
)
// التغييرات داخل RepaintBoundary لا تسبب
// إعادة رسم الأشقاء أو الآباء
RepaintBoundary حول الودجات التي تُعاد رسمها بشكل متكرر (مثل الرسوم المتحركة) لمنع إعادة الرسم غير الضرورية لبقية الشجرة. يمكن لـ DevTools في Flutter عرض مناطق إعادة الرسم لتحديد فرص التحسين.فحص شجرة الودجات باستخدام DevTools
Flutter DevTools هي مجموعة من أدوات الأداء والتصحيح التي تتيح لك فحص شجرة الودجات وتحليل الأداء وتصحيح التخطيطات والمزيد.
استخدام Flutter DevTools
// تشغيل DevTools من سطر الأوامر:
flutter run
// ثم اضغط 'v' في الطرفية لفتح DevTools
// أو تشغيل DevTools مباشرة:
dart devtools
// أو من VS Code:
// شغّل تطبيقك، ثم انقر "Open DevTools" في شريط أدوات التصحيح
// ميزات مفتش الودجات:
// 1. عرض شجرة الودجات
// - يعرض التسلسل الكامل للودجات
// - انقر أي ودجة لرؤية خصائصها
// - يبرز الودجة المحددة على الجهاز
// 2. مستكشف التخطيط
// - تمثيل مرئي لتخطيطات Flex
// - يعرض القيود والأحجام وعوامل المرونة
// - تفاعلي: انقر لتعديل القيم
// 3. تفاصيل الودجة
// - جميع خصائص الودجة المحددة
// - حجم وموقع RenderObject
// - أي ملف/سطر أنشأ هذه الودجة
// تصحيح شائع مع DevTools:
// - "لماذا ودجتي غير مرئية؟"
// تحقق من القيود - قد يكون حجمها صفر
// - "لماذا يوجد تجاوز؟"
// تحقق إذا كان الأبناء يتجاوزون قيود الأب
// - "لماذا التخطيط خاطئ؟"
// استخدم مستكشف التخطيط لرؤية عوامل المرونة
أعلام التصحيح لفحص شجرة الودجات
import 'package:flutter/rendering.dart';
void main() {
// عرض قيود وأحجام التخطيط
debugPaintSizeEnabled = true;
// عرض خطوط الأساس
debugPaintBaselinesEnabled = true;
// عرض حدود إعادة الرسم
debugRepaintRainbowEnabled = true;
// عرض حدود الطبقات
debugPaintLayerBordersEnabled = true;
// طباعة شجرة الودجات في وحدة التحكم
debugDumpApp();
// طباعة شجرة العرض في وحدة التحكم
debugDumpRenderTree();
// طباعة شجرة الطبقات في وحدة التحكم
debugDumpLayerTree();
runApp(const MyApp());
}
أمثلة عملية: بناء أشجار ودجات
دعنا نبني أشجار ودجات معقدة تدريجياً لتعزيز فهمك لكيفية تركيب الودجات معاً.
مثال 1: بطاقة ملف شخصي بسيطة
شجرة ودجات بطاقة الملف الشخصي
// تصور شجرة الودجات:
// Card
// +-- Padding
// +-- Row
// +-- CircleAvatar
// | +-- Text('ES')
// +-- SizedBox(width: 16)
// +-- Column
// +-- Text('Edrees Salih')
// +-- Text('Flutter Developer')
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
const CircleAvatar(
radius: 30,
child: Text('ES'),
),
const SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Edrees Salih',
style: Theme.of(context).textTheme.titleMedium,
),
Text(
'Flutter Developer',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
],
),
),
)
مثال 2: تخطيط تنقل متداخل
شجرة ودجات متداخلة معقدة
// شجرة ودجات عميقة بمستويات متعددة:
// MaterialApp
// +-- Scaffold
// +-- AppBar
// | +-- Text('Dashboard')
// +-- body: SafeArea
// | +-- SingleChildScrollView
// | +-- Padding
// | +-- Column
// | +-- _buildHeader() [شجرة فرعية]
// | +-- SizedBox
// | +-- _buildStats() [شجرة فرعية]
// | +-- SizedBox
// | +-- _buildActions() [شجرة فرعية]
// +-- bottomNavigationBar: BottomNavigationBar
// +-- BottomNavigationBarItem(الرئيسية)
// +-- BottomNavigationBarItem(البحث)
// +-- BottomNavigationBarItem(الملف الشخصي)
// تقسيم أشجار الودجات إلى دوال يحسن القراءة:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Dashboard')),
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
_buildHeader(),
const SizedBox(height: 24),
_buildStats(),
const SizedBox(height: 24),
_buildActions(),
],
),
),
),
),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'الرئيسية',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'البحث',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'الملف الشخصي',
),
],
),
);
}
مثال 3: فهم إعادة البناء
إعادة بناء الودجات عملياً
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _count = 0;
@override
Widget build(BuildContext context) {
print('CounterPage build called'); // يسجل كل إعادة بناء
return Scaffold(
appBar: AppBar(
// ودجة const هذه لا تُعاد بناؤها عند استدعاء setState
title: const Text('Counter Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// ودجة const هذه لا تُعاد بناؤها
const Text('Current count:'),
// هذه الودجة تُعاد بناؤها (تعتمد على _count)
Text(
'\$_count',
style: Theme.of(context).textTheme.displayLarge,
),
const SizedBox(height: 20),
// ودجة const هذه لا تُعاد بناؤها
const Icon(Icons.touch_app, size: 48),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_count++;
// setState تعلّم هذه الودجة كمتسخة
// يعيد Flutter بناء CounterPage وشجرتها الفرعية
// لكن ودجات const تُتخطى!
});
},
child: const Icon(Icons.add),
),
);
}
}
build() واحدة. استخرج الأشجار الفرعية إلى فئات ودجات منفصلة أو دوال مساعدة. هذا يحسن القراءة ويمكّن تحسين إعادة البناء بشكل أفضل (الودجات المنفصلة يمكنها إعادة البناء بشكل مستقل) ويجعل كودك أسهل في الاختبار والصيانة.المفاتيح: الحفاظ على الحالة عبر إعادة البناء
المفاتيح تخبر Flutter كيف يطابق الودجات بين إعادة البناء. بدون مفاتيح، يطابق Flutter الودجات بنوعها وموقعها. مع المفاتيح، يمكن لـ Flutter مطابقة الودجات حتى عندما يتغير موقعها.
متى تهم المفاتيح
// بدون مفاتيح - Flutter يطابق بالموقع:
// قبل: [TodoItem('شراء حليب'), TodoItem('المشي مع الكلب')]
// بعد: [TodoItem('المشي مع الكلب')]
// Flutter يظن: "العنصر الأول تغير من شراء حليب إلى المشي"
// النتيجة: خاطئ - حالة العنصر الأول تُستخدم للمهمة الخاطئة
// مع مفاتيح - Flutter يطابق بالمفتاح:
// قبل: [TodoItem(key: Key('1'), 'شراء حليب'), TodoItem(key: Key('2'), 'المشي مع الكلب')]
// بعد: [TodoItem(key: Key('2'), 'المشي مع الكلب')]
// Flutter يظن: "العنصر بمفتاح 1 أُزيل، العنصر بمفتاح 2 يبقى"
// النتيجة: صحيح - الحالة تُحفظ للمهمة الصحيحة
// أنواع المفاتيح:
ValueKey('unique_string') // بناءً على قيمة
ValueKey(item.id) // بناءً على معرف
ObjectKey(myObject) // بناءً على هوية الكائن
UniqueKey() // فريد دائماً (استخدمه بحذر)
GlobalKey() // فريد عبر التطبيق بالكامل
// القاعدة: استخدم المفاتيح عندما يكون لديك قائمة
// ودجات ذات حالة يمكن إعادة ترتيبها أو إزالتها
ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return TodoItem(
key: ValueKey(todos[index].id), // استخدم المعرف الفريد كمفتاح
todo: todos[index],
);
},
)
الملخص
في هذا الدرس، اكتسبت فهماً عميقاً لشجرة ودجات Flutter. تعلمت أن الودجات كائنات تكوين غير قابلة للتغيير وليست عناصر واجهة المستخدم الفعلية. استكشفت تسلسل شجرة الودجات وعلاقات الأب والابن، وفهمت كيف يوفر BuildContext الوصول لودجات الأسلاف، وتتبعت كيف يبني Flutter واجهة المستخدم عبر مراحل البناء والتخطيط والرسم. تعلمت عن createElement() ودورة حياة العنصر، وأهمية عدم قابلية الودجات للتغيير ومُنشئات const، وكيفية فحص أشجار الودجات باستخدام DevTools، وأنماط عملية لتركيب أشجار الودجات. أخيراً، فهمت دور المفاتيح في الحفاظ على الحالة عبر إعادة البناء. هذه المعرفة تشكل الأساس لكل ما ستبنيه في Flutter.