شجرة RenderObject هي المستوى الثالث في التسلسل الهرمي لـ Flutter، وهي المسؤولة عن العرض الفعلي للواجهة على الشاشة. كل عقدة في هذه الشجرة تقوم بحساب الأحجام (layout) والرسم (painting)، محولة تكوين الويجتات إلى بكسلات. بخلاف Widget Tree، فإن شجرة RenderObject تُنشأ مرة واحدة وتتحدث فقط عند تغير الهندسة أو المظهر. وفقًا لـ مرجع API Flutter، 2025، فإن كفاءة شجرة RenderObject تحدد مباشرة سلاسة الرسوم المتحركة وسرعة استجابة التطبيق.
النقاط الرئيسية
شجرة RenderObject هي بنية هرمية من كائنات العرض التي تعمل كتمثيل فعلي للواجهة في الذاكرة. كل RenderObject يعرف حجمه وموقعه على الشاشة وكيفية رسم نفسه. بخلاف الويجتات، التي خفيفة ويمكن إعادة إنشائها مئات المرات في الثانية، فإن RenderObject هو كائن ثقيل يملك وصولًا مباشرًا إلى خط الأنابيب الرسومي.
يمر المسار من الويجت إلى البكسلات بثلاث مراحل: يصف Widget Tree ما يجب أن يكون على الشاشة؛ يدير Element Tree دورة الحياة؛ وتقوم شجرة RenderObject بالعمل الفعلي. كل RenderObjectWidget (مثل Padding، Transform، CustomPaint) ينشئ RenderObject يتم إضافته إلى شجرة العرض. RenderBox هو النوع الأكثر شيوعًا من RenderObject، ويستخدم في 99% من الويجتات القياسية.
RenderObject هو صف مجرد يحدد واجهة التفاعل للتنسيق والرسم. يحتوي على مراجع لـ RenderObject الأب والكائنات التابعة، وكذلك طرق مجردة: performLayout، paint و hitTest. كل RenderObject محدد ينفذ هذه الطرق وفقًا لسلوكه: RenderFlex يوزع المساحة بين العناصر التابعة، RenderImage يعرض صورة، RenderParagraph يعرض نصًا.
abstract class RenderObject {
RenderObject? parent;
Constraints constraints;
ParentData? parentData;
bool _needsLayout = true;
bool _needsPaint = true;
void performLayout();
void paint(PaintingContext context, Offset offset);
}
في هذه البنية المبسطة، يحتوي RenderObject على العلامات _needsLayout و _needsPaint، التي تُعلم الكائن كمحتاج للتحديث. عندما تتغير Widget Tree، تُعلم Element Tree كائنات RenderObject المقابلة كـ «متسخة» للتنسيق أو الرسم، وفي الإطار التالي ينفذ Flutter فقط العمليات الضرورية.
كل RenderObject ينفذ مرحلتين رئيسيتين: layout (تحديد الأحجام والمواقع) و paint (الرسم). تتنفذ هذه المراحل بترتيب صارم: أولًا layout للشجرة بأكملها، ثم paint. يمكن تخطي مرحلة paint إذا لم يتغير layout، مما يوفر موارد GPU.
أثناء layout، ينقل Flutter القيود (constraints) من أعلى لأسفل عبر شجرة RenderObject. يحدد كل أب أدنى وأقصى عرض وارتفاع للعنصر التابع. يحسب العنصر التابع حجمه ضمن هذه القيود ويعيده إلى الأب. يطلق على هذه العملية اسم «المرور التنازلي» (نقل القيود لأسفل) و«المرور التصاعدي» (نقل الأحجام لأعلى).
بعد اكتمال layout، ينفذ Flutter عملية paint — رسم كل RenderObject. ينشئ الأب PaintingContext، يمرره إلى الكائنات التابعة ويحدد الإزاحة (offset). يرسم كل RenderObject نفسه على Canvas باستخدام الأوليات الرسومية: المستطيلات، الدوائر، النصوص، الصور والتحويلات. يتفاعل Canvas مباشرة مع Skia أو Impeller.
يستخدم Flutter آلية العقد المتسخة لتقليل العمل. عندما يتغير حجم RenderObject واحد فقط، لا يعيد Flutter حساب layout للشجرة بأكملها — بل يسم العقدة المتغيرة وأسلافها حتى أقرب RenderBox بحجم ثابت. بالمثل، عندما يتغير المظهر، يتم وسم paint فقط دون إعادة layout.
Flutter يوفر عدة فئات من RenderObject لمهام مختلفة: RenderBox للعناصر المستطيلة القياسية، RenderSliver للمناطق القابلة للتمرير، و RenderObjects مخصصة للعرض غير القياسي عبر CustomPainter. كل نوع محسن لدوره في شجرة العرض.
RenderBox هو الصف الأساسي لعناصر الواجهة المستطيلة. تشمل أنواعه الفرعية: RenderPadding (يضيف حشوات)، RenderTransform (يطبق تحويلات)، RenderFlex (ينفذ Row و Column)، RenderStack (يطبق عناصر متطابقة)، RenderImage (يعرض صوراً)، RenderParagraph (يعرض نصًا). كل نوع فرعي يغطي performLayout و paint لمنطقه الخاص.
RenderSliver هو نوع من RenderObject للعمل مع المناطق القابلة للتمرير. بخلاف RenderBox، يستخدم Sliver نوع SliverConstraints، الذي يتضمن معلومات حول المنظر (viewport). RenderSliverList و RenderSliverGrid يُنشئان RenderObjects فقط للعناصر التي تظهر في المنظر، مما يتيح التعامل مع قوائم تحتوي على ملايين الإدخالات.
للرسوم غير القياسية، استخدم CustomPaint و CustomPainter. CustomPaint يُنشئ RenderCustomPaint، الذي يستدعي طرق paint من CustomPainter. يتيح ذلك رسم أشكال ورسومات ورسوم متحركة بسيطرة كاملة على Canvas. وفقًا لفريق Flutter، فإن CustomPainter أكفاء من الويجتات القياسية المتداخلة للرسوم المتجهة المعقدة.
class CirclePainter extends CustomPainter {
final Color color;
CirclePainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color;
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
size.width / 3,
paint,
);
}
@override
bool shouldRepaint(CirclePainter oldDelegate) =>
oldDelegate.color != color;
}
في هذا المثال، يرسم CirclePainter دائرة على Canvas. تعيد طريقة shouldRepaint قيمة true فقط عند تغير اللون، مما يمنع إعادات الرسم غير الضرورية. يجب أن يكون CustomPainter خفيفًا قدر الإمكان — أجر جميع الحسابات الثقيلة خارج طرق paint.
شجرة RenderObject تُنشأ من Element Tree عبر آلية RenderObjectWidget. كل RenderObjectWidget (Padding، Transform، CustomPaint) يُنشئ RenderObjectElement، الذي بدوره يُنشئ ويدير RenderObject المقابل. يعمل العنصر كوسيط: ينقل التكوين من الويجت إلى RenderObject ويخبر RenderObject بالتغييرات.
عندما يتم تثبيت RenderObjectElement، يستدعي طريقة createRenderObject من الويجت الخاص به. يُنشئ الويجت مثال RenderObject ويعيده إلى العنصر. يُدرج العنصر RenderObject في شجرة RenderObject باستدعاء طريقة insertChildLayout من RenderObject الأب. تحدث هذه العملية فقط عند التثبيت الأول — في التحديثات اللاحقة، يقوم العنصر ببساطة تحديث معلمات RenderObject الموجود.
عندما تتغير تكوينة الويجت (على سبيل المثال، قيمة padding)، يستدعي العنصر طريقة updateRenderObject، التي تنقل التكوين الجديد إلى RenderObject الموجود. يسم RenderObject نفسه كـ «متسخ» للتنسيق أو الرسم، وفي الإطار التالي ينفذ الإطار التحديثات الضرورية.
عند إلغاء تثبيت عنصر، تستدعى طريقة unmount، التي تزيل RenderObject من شجرة RenderObject وتحرر الموارد. يتم استدعاء RenderObject.remove لفصله عن الأب، بعد ذلك يمكن جمع الكائن بواسطة مجمع القمامة. يضمن Flutter عدم وجود RenderObject معلق في الشجرة دون عنصر مقابل.
RepaintBoundary هو ويجت يُنشئ طبقة منفصلة لعرض محتواه. عندما يتغير المحتوى داخل RepaintBoundary، تتم إعادة رسم تلك الطبقة فقط، ويبقى باقي الشاشة دون تغيير. RepaintBoundary مفيد خاصة للرسوم المتحركة، مشغلات الفيديو، الرسوم البيانية التفاعلية والعناصر الأخرى التي تتحدث بشكل متكرر.
في جوهر RepaintBoundary يكمن RenderRepaintBoundary — RenderObject خاص يُنشئ PictureLayer منفصلاً. في أول عرض، يسجل RenderRepaintBoundary أوامر الرسم في هذه الطبقة. في التحديثات اللاحقة، إذا تغير المحتوى داخل RepaintBoundary فقط، يعيد Flutter رسم هذه الطبقة فقط وليس الشاشة بأكملها. تبقى الطبقات الأخرى دون تغيير وتُعاد استخدامها.
ليس كل ويجت يحتاج RepaintBoundary. استخدمه عندما يتحدث جزء من الواجهة بتردد عالي (60 FPS أو أكثر) بينما باقي الشاشة ثابت. أمثلة نموذجية: مؤشر تحميل متحرك، مشغل فيديو، Canvas لعبة، CustomPainter بإعادات رسم متكررة. للنص الثابت أو الأزرار، RepaintBoundary زائد ويزيد فقط في استهلاك الذاكرة.
| السيناريو | التوصية | السبب |
|---|---|---|
| رسوم متحركة | لف في RepaintBoundary | يعزل المنطقة المتحدثة بكثرة |
| قائمة قابلة للتمرير | غير مطلوب | ListView تستخدم الطبقات تلقائيًا |
| نص ثابت | غير مطلوب | لا توجد إعادات رسم متكررة |
| CustomPainter | موصى به | إعادات رسم رسومية متكررة |
الأداء لشجرة RenderObject يعتمد على عدد العقد، عمق الشجرة وتكرار إعادة الرسم. توفر Flutter DevTools (علامة التبويب «Rendering») التحليل الفوري لشجرة RenderObject: عدد إعادات الرسم، وقت layout و paint، عدد الطبقات وأحجامها. يساعد التحليل المنتظم في تحديد الاخناقات.
Overdraw هو موقف يتم فيه رسم بكسل واحد عدة مرات في الإطار الواحد. على سبيل المثال، عندما يتداخل ويجت شفاف مع آخر، ترسم GPU كلتا الطبقتين. استخدم علامات غير شفافة (Container باستخدام color بدل decoration) للعناصر غير الشفافة لكي يتجاوز Flutter الطبقات غير المرئية. وفقًا لفريق Flutter، قل ال overdraw يمكن أن يقلل وقت paint بنسبة تصل إلى 30%.
كل RepaintBoundary وبعض الويجتات (Opacity، ClipRRect، Transform) تُنشئ طبقة منفصلة (PictureLayer). الطبقات الكثيرة تزيد وقت التركيب. استخدم التجميع: بدلاً من ويجتات Opacity متعددة على عناصر فردية، طبق Opacity واحدًا على الحاوية. بدلاً من ClipRRect على كل عنصر، استخدم ClipRRect على الحاوية المشتركة.
عندما يتم تصريح ويجت كـ const، يعلم Flutter أن تكوينته لن تتغير ويمكنه إعادة استخدام RenderObject المقابل دون إعادة إنشائه. تقلل المنشئات const عبء مجمع القمامة وتسرع أول إطار. استخدم const للويجتات ذات المعلمات الثابتة: الأيقونات، العناوين، العناصر الزخرفية.
const Text("عنوان", style: TextStyle(fontSize: 24));
const Icon(Icons.home, color: Colors.blue);
const SizedBox.shrink();
الأسئلة الشائعة
Widget Tree هي تكوين خفيف للواجهة يتم إعادة إنشائه في كل إعادة بناء. شجرة RenderObject هي شجرة ثقيلة من كائنات العرض تبقى وتتحدث فقط عند تغير الهندسة أو المظهر.
استخدم Flutter DevTools — علامة التبويب «Rendering». سترى طبقات العرض، وقت layout و paint، ومعلومات مفصلة عن كل RenderObject: الأحجام، القيود وعلامات الاحتياج للتحديث.
Dirty هو RenderObject معلم عليه بأنه يحتاج تحديثًا. يتم تعيين العلامة _needsLayout أو _needsPaint عند تغيير التكوينة، وينفذ Flutter layout أو paint لهذه العقدة في الإطار التالي.
نعم، أنشئ صفًا فرعيًا من RenderBox وغطي طريقتي performLayout و paint. استخدم RenderObjectWidget لتضمين RenderObject مخصص في Widget Tree. هذه تقنية متقدمة للعرض غير القياسي.
RepaintBoundary يخلق RenderRepaintBoundary، الذي يعزل جزءًا من شجرة RenderObject في طبقة منفصلة. عند تغير المحتوى، تتم إعادة رسم تلك الطبقة فقط، ويبقى باقي الشجرة دون تغيير.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا