كيف يعمل الرفرفة


كيف ترفرف فعلا العمل؟


ما هي الحاجيات ، العناصر ، BuildContext ، RenderOject ، الارتباطات؟ ..


الصعوبة: مبتدئ


دخول


في العام الماضي ( ملاحظة: في عام 2018 ) ، عندما بدأت رحلتي إلى عالم رائع من Flutter ، كان هناك القليل جدًا من المعلومات على الإنترنت مقارنة بما هي عليه اليوم. الآن ، على الرغم من حقيقة أنه قد تم بالفعل كتابة الكثير من المواد ، يتحدث جزء صغير منها عن كيفية عمل Flutter بالفعل.


ما هي الحاجيات ( الحاجيات ) ، العناصر ( العناصر ) ، BuildContext؟ لماذا يرفرف بسرعة؟ لماذا في بعض الأحيان أنها لا تعمل كما هو متوقع؟ ما هي الأشجار ولماذا هم في حاجة؟


في 95٪ من الحالات عند كتابة طلب ، سوف تتعامل فقط مع التطبيقات المصغّرة لعرض شيء ما أو التفاعل معه. لكن ألم تتسائل أبدًا كيف يعمل هذا السحر في الداخل؟ كيف يعرف النظام موعد تحديث الشاشة والأجزاء التي يجب تحديثها؟


المحتويات:



الجزء 1: الخلفية


يقدم الجزء الأول من المقالة بعض المفاهيم الأساسية التي سيتم استخدامها في الجزء الثاني من المادة وتساعد على فهم أفضل للرفرفة.


قليلا عن الجهاز


لنبدأ من النهاية ونعود إلى الأساسيات.


عندما تنظر إلى جهازك أو بتعبير أدق إلى التطبيق الذي يعمل على جهازك ، فإنك ترى الشاشة فقط.


في الواقع ، كل ما تراه هو البيكسلات ، التي تشكل معًا صورة ثنائية الأبعاد ، وعندما تلمس الشاشة بإصبعك ، يتعرف الجهاز فقط على وضع إصبعك على الزجاج.


كل سحر التطبيق (من وجهة نظر مرئية) في معظم الحالات هو تحديث هذه الصورة على أساس التفاعلات التالية:


  • مع شاشة الجهاز ( على سبيل المثال ، إصبع على الزجاج )
  • مع الشبكة ( على سبيل المثال ، التواصل مع الخادم )
  • بمرور الوقت ( مثل الرسوم المتحركة )
  • مع أجهزة الاستشعار الخارجية الأخرى

يتم توفير رؤية الصورة على الشاشة بواسطة الأجهزة (العرض) ، والتي تقوم بانتظام بتحديث العرض (عادةً 60 مرة في الثانية). وهذا ما يسمى "معدل التحديث" ويتم التعبير عنها بالهرتز (Hertz).


تتلقى الشاشة معلومات للعرض من GPU (وحدة معالجة الرسومات) ، وهي دائرة إلكترونية متخصصة تم تحسينها وتصميمها لتكوين صور بسرعة من بعض البيانات (المضلعات والقوام). يُطلق على عدد المرات في الثانية التي يستطيع فيها معالج الرسومات إنشاء "صورة" (= مخزن مؤقت للإطار) لعرضه وإرساله إلى الجهاز باسم معدل الإطار ( ملاحظة: معدل الإطارات ). يتم قياس ذلك باستخدام كتلة من الإطارات في الثانية ( مثل 60 إطارًا في الثانية أو 60 إطارًا في الثانية ).


قد تسألني لماذا بدأت هذه المقالة مع مفاهيم صورة ثنائية الأبعاد معروضة بواسطة وحدة معالجة الرسومات / جهاز استشعار الزجاج الفعلي ، وما هو الاتصال مع الأدوات الذكية Flutter؟


أعتقد أنه سيكون من الأسهل فهم كيفية عمل Flutter بالفعل إذا نظرنا إليها من وجهة النظر هذه ، لأن أحد الأهداف الرئيسية لتطبيق Flutter هو إنشاء هذه الصورة ثنائية الأبعاد والسماح لها بالتفاعل معها. أيضًا لأنه في Flutter ، صدق أو لا تصدق ، يرجع كل شيء تقريبًا إلى الحاجة إلى تحديث الشاشة بسرعة وفي الوقت المناسب!


واجهة بين الرمز والجهاز


على أي حال ، فإن جميع المهتمين بـ Flutter شاهدوا بالفعل الصورة التالية التي تصف بنية Flutter عالية المستوى .



عندما نكتب تطبيق Flutter باستخدام Dart ، سنظل على مستوى Flutter Framework (مظلل باللون الأخضر).


يتفاعل Flutter Framework مع Flutter Engine (باللون الأزرق) من خلال طبقة تجريدية تسمى Window . يوفر هذا المستوى من التجريد عددًا من واجهات برمجة التطبيقات للتفاعل غير المباشر مع الجهاز.


من خلال هذا المستوى من التجريد أيضًا ، يقوم Flutter Engine بإخطار Flutter Framework عندما:


  • يحدث حدث مهم على مستوى الجهاز (تغيير الاتجاه ، تغيير الإعدادات ، مشكلة الذاكرة ، حالة تشغيل التطبيق ...)
  • يحدث بعض الأحداث على مستوى الزجاج (= لفتة)
  • قناة منصة يرسل بعض البيانات
  • ولكن أيضًا بشكل أساسي عندما يكون Flutter Engine جاهزًا لتقديم إطار جديد

إدارة رفرفة الإطار رفرفة المحرك تقديم


من الصعب تصديق ذلك ، لكن هذا صحيح. باستثناء في بعض الحالات ( انظر أدناه ) ، لا يتم تنفيذ أي كود Flutter Framework دون بدء تقديم Flutter Engine .


استثناءات:


  • لفتة / لفتة (= حدث على الزجاج)
  • رسائل النظام الأساسي (= الرسائل التي تم إنشاؤها بواسطة جهاز ، مثل GPS)
  • رسائل الجهاز (= الرسائل المتعلقة بتغيير في حالة الجهاز ، على سبيل المثال ، الاتجاه ، التطبيق المرسل في الخلفية ، تنبيهات الذاكرة ، إعدادات الجهاز ...)
  • ردود المستقبل أو HTTP

(بيننا ، يمكنك تطبيق تغيير مرئي بالفعل دون الاتصال من محرك Flutter ، لكن هذا غير مستحسن )


أنت تسألني: "إذا تم تنفيذ نوع من التعليمات البرمجية المتعلقة بالإيماءة وتسبب في تغيير مرئي ، أو إذا كنت تستخدم مؤقتًا لتعيين وتيرة المهمة التي تؤدي إلى تغييرات مرئية (على سبيل المثال ، الرسوم المتحركة) ، فكيف يعمل؟"


إذا كنت ترغب في حدوث تغيير مرئي أو تنفيذ بعض التعليمات البرمجية على أساس مؤقت ، فأنت بحاجة إلى إخبار محرك Flutter Engine بأنه يجب رسم شيء ما.


عادةً ما يتم تحديث Flutter Engine في المرة القادمة ، فإنه يستدعي Flutter Framework لتنفيذ بعض التعليمات البرمجية ، ويوفر في النهاية مشهدًا جديدًا للتقديم.


لذلك ، فإن السؤال المهم هو كيف ينظم محرك Flutter كل سلوك التطبيق بناءً على العرض.


للحصول على فكرة عن الآليات الداخلية ، انظر إلى الرسوم المتحركة التالية:



شرح موجز (ستأتي التفاصيل لاحقًا):


  • يمكن لبعض الأحداث الخارجية (الإيماءات ، واستجابات المتشعب ، وما إلى ذلك) أو حتى العقود المستقبلية تشغيل المهام التي تجعل من الضروري تحديث العرض. يتم إرسال الرسالة المقابلة إلى محرك الرفرفة (= جدول الإطار )
  • عندما يكون Flutter Engine جاهزًا لبدء تحديث التقديم ، فإنه ينشئ طلبًا لبدء الإطار
  • يتم اعتراض طلب إطار البداية هذا من قبل Flutter Framework ، والذي يؤدي المهام المرتبطة بشكل رئيسي بالعلامات (على سبيل المثال ، الرسوم المتحركة)
  • يمكن لهذه المهام إعادة إنشاء طلب التقديم لاحقًا (على سبيل المثال: الحركة لم تكمل تنفيذها ، ولإكمالها ، ستحتاج إلى الحصول على إطار بيغن آخر في مرحلة لاحقة)
  • بعد ذلك ، يرسل Flutter Engine إطار رسم ، والذي يتم اعتراضه بواسطة Flutter Framework ، والذي سيبحث عن أي مهام متعلقة بتحديث التصميم من حيث البنية والحجم
  • بعد الانتهاء من جميع هذه المهام ، ينتقل إلى المهام المرتبطة بتحديث المخطط من حيث العرض
  • إذا كان هناك شيء ما على الشاشة يجب رسمه ، فسيتم إرسال مشهد جديد ( مشهد ) للتصور إلى محرك Flutter ، والذي سيؤدي إلى تحديث الشاشة
  • يقوم Flutter Framework بعد ذلك بتنفيذ جميع المهام التي سيتم تنفيذها بعد التقديم (= عمليات رد PostFrame) وأي مهام أخرى لاحقة لا تتعلق بالتقديم
  • ... وهذه العملية تبدأ من جديد

RenderView و RenderObject


قبل الغوص في تفاصيل سير العمل ، حان الوقت لتقديم مفهوم شجرة التقديم .


كما ذكرنا سابقًا ، سيتم تحويل كل شيء في النهاية إلى وحدات بكسل سيتم عرضها على الشاشة ، وسيحول إطار عمل الرفرفة الأدوات التي نستخدمها لتطوير التطبيق إلى كتل بصرية سيتم عرضها على الشاشة.


تتوافق هذه الأجزاء المرئية مع كائنات تسمى RenderObject ، والتي تستخدم في:


  • تحديد مساحة معينة من الشاشة من حيث الحجم والموقع والهندسة ، وكذلك من حيث "المحتوى المقدم"
  • تحديد مناطق الشاشة التي يمكن أن تتأثر بالإيماءات (= لمسة إصبع)

تشكل مجموعة من جميع RenderObjects شجرة تدعى Render Tree . في الجزء العلوي من هذه الشجرة (= الجذر ) نجد RenderView .


يوفر RenderView سطحًا مشتركًا لكائنات Render Tree وهو إصدار خاص من RenderObject .


بصريا ، يمكننا تمثيل كل هذا على النحو التالي:


ستتم مناقشة العلاقة بين Widget و RenderObject لاحقًا. في غضون ذلك ، حان الوقت للتعمق قليلاً ...


ربط التهيئة


عند بدء تشغيل تطبيق runApp(Widget app) ، تسمى الوظيفة main() أولاً ، والتي تستدعي في runApp(Widget app) طريقة تشغيل runApp(Widget app) .


عندما يتم runApp() طريقة runApp() Flutter Framework بتهيئة الواجهات بينه وبين محرك Flutter . تسمى هذه واجهات الربط ( ملاحظة: الربط ).


مقدمة إلى الارتباطات


الروابط مصممة لتكون الرابط بين الإطار ومحرك الرفرفة. فقط من خلال الارتباطات يمكن تبادل البيانات بين Flutter Framework و Flutter Engine .
(هناك استثناء واحد فقط لهذه القاعدة - RenderView ، لكننا سنناقش هذا لاحقًا).


كل رابط مسؤول عن معالجة مجموعة من المهام والإجراءات والأحداث المحددة ، مجمعة حسب مجال النشاط.


في وقت كتابة هذا التقرير ، كان لدى Flutter Framework 8 روابط.


أدناه 4 منها التي سيتم النظر فيها في هذه المقالة:


  • SchedulerBinding
  • لفتة ملزمة
  • العارض ملزم
  • الحاجيات ملزمة

للتأكد من اكتمالها ، سأذكر الأربعة المتبقية:


  • ServicesBinding : مسؤول عن معالجة الرسائل المرسلة بواسطة قناة النظام الأساسي
  • PaintingBinding : مسؤولة عن معالجة ذاكرة التخزين المؤقت للصور
  • SemanticsBinding : مخصص للتنفيذ اللاحق لكل ما يتعلق بالدلالات
  • TestWidgetsFlutterBinding : يستخدم من قبل مكتبة اختبار عنصر واجهة المستخدم

يمكنك أيضًا ذكر WidgetsFlutterBinding ، لكن هذا ليس ملزماً حقًا ، ولكنه نوع من "مُهيئ الربط" .


يوضح الرسم البياني التالي التفاعل بين الروابط ، والتي سأنظر فيها بعد ذلك ، ومحرك الرفرفة .



دعنا ننظر إلى كل من هذه الروابط "الأساسية".


SchedulerBinding


هذا الربط له مسؤوليتان رئيسيتان:


  • قل Flutter Engine : "مهلا! في المرة القادمة عندما لا تكون مشغولاً ، استيقظي حتى أتمكن من العمل قليلاً وأخبرك بما تريد تقديمه ، أو إذا كنت بحاجة إلى الاتصال بي لاحقًا ..."
  • استمع إلى مثل هذه "الصحوات المزعجة" والرد عليها (انظر أدناه)

متى تطلب خدمة SchedulerBinding مكالمة إيقاظ ؟


  • عندما يجب أن يعمل المؤشر على علامة جديدة


    على سبيل المثال ، لديك رسم متحرك ، يمكنك تشغيله. يتم اقتصاص الرسوم المتحركة باستخدام Ticker ، والذي يتم استدعاؤه على فترات منتظمة (= علامة ) لإجراء رد اتصال . من أجل إطلاق رد اتصال من هذا القبيل ، نحتاج إلى إخبار محرك Flutter بحيث يستيقظ لنا أثناء التحديث التالي (= Begin Frame ). سيؤدي هذا إلى تشغيل رد الاتصال المؤشر لإكمال مهمته. إذا كان لا يزال يتعين على المؤشر متابعة التنفيذ ، فسوف يقوم في نهاية المهمة باستدعاء SchedulerBinding لجدولة إطار آخر.


  • عندما لتحديث العرض


    على سبيل المثال ، نحتاج إلى إجراء حدث يؤدي إلى تغيير مرئي (على سبيل المثال: تحديث لون جزء من الشاشة ، التمرير ، إضافة / إزالة شيء من الشاشة) ، لهذا نحتاج إلى اتخاذ الخطوات اللازمة لعرض الصورة المحدثة في النهاية على الشاشة. في هذه الحالة ، عند حدوث مثل هذا التغيير ، يقوم Flutter Framework باستدعاء SchedulerBinding لجدولة إطار آخر باستخدام Flutter Engine . (في وقت لاحق سوف نرى كيف يعمل هذا في الواقع)



لفتة ملزمة


يستمع هذا الربط إلى التفاعل مع المحرك من حيث "الإصبع" (= لفتة ).


وبصفة خاصة ، يكون مسؤولاً عن تلقي البيانات المتعلقة بالإصبع وعن تحديد الجزء (الأجزاء) من الشاشة التي تعمل بها الإيماءات. ثم يخطر وفقا لذلك / من هذه الأجزاء.


العارض ملزم


هذا الربط هو الرابط بين Flutter Engine و Render Tree . هي المسؤولة عن:


  • الاستماع إلى الأحداث التي تم إنشاؤها بواسطة المحرك للإبلاغ عن التغييرات التي يطبقها المستخدم من خلال إعدادات الجهاز التي تؤثر على المؤثرات المرئية و / أو الدلالات
  • رسالة إلى المحرك حول التغييرات التي سيتم تطبيقها على الشاشة

لتوفير التغييرات التي سيتم عرضها على الشاشة ، يكون RendererBinding مسؤولاً عن إدارة PipelineOwner وتهيئة RenderView .


PipelineOwner هي نوع من الأوركسترا التي تعرف ما يجب القيام به مع RenderObject وفقًا للمكون ، وتنسق هذه الإجراءات.


الحاجيات ملزمة


يستمع هذا الربط إلى التغييرات التي يطبقها المستخدم من خلال إعدادات الجهاز التي تؤثر على اللغة (= الإعدادات المحلية ) والدلالات .


ملاحظة صغيرة

أفترض أنه في مرحلة لاحقة من تطور Flutter ، سيتم نقل جميع الأحداث المتعلقة بالدلالات إلى SemanticsBinding ، ولكن في وقت كتابة هذا التقرير ، لم تكن هذه هي الحالة.

بالإضافة إلى ذلك ، فإن WidgetsBinding هو الرابط بين الحاجيات ومحرك الرفرفة . هي المسؤولة عن:


  • إدارة عملية معالجة التغييرات هيكل القطعة
  • تقديم دعوة

تتم معالجة التغييرات في هيكل الحاجيات باستخدام BuildOwner .


يقوم BuildOwner بتتبع الأدوات التي تحتاج إلى إعادة إنشائها ، ويتعامل مع المهام الأخرى التي تنطبق على بنية عنصر واجهة المستخدم ككل.


الجزء 2. من الحاجيات إلى بكسل


الآن وقد تعلمنا أساسيات العمل الداخلي لـ Flutter ، فقد حان الوقت للتحدث عن التطبيقات المصغّرة.


في جميع وثائق Flutter ، سوف تقرأ أن جميع الحاجيات (الحاجيات).


هذا صحيح تقريبا. ولكي أكون أكثر دقة ، أود أن أقول:


من جانب المطور ، يتم كل ما يتعلق بواجهة المستخدم من حيث التخطيط والتفاعل باستخدام التطبيقات المصغرة.

لماذا الكثير من الدقة؟ إلى جانب حقيقة أن القطعة تتيح للمطور تحديد جزء من الشاشة من حيث الحجم والمحتوى والتخطيط والتفاعل ، إلا أن هناك ما هو أكثر من ذلك بكثير. فما هي القطعة حقا؟


التكوين غير قابل للتغيير


إذا نظرت إلى شفرة مصدر Flutter ، ستلاحظ التعريف التالي لفئة القطعة .


 @immutable abstract class Widget extends DiagnosticableTree { const Widget({ this.key }); final Key key; ... } 

ماذا يعني هذا؟


يعد التعليق التوضيحي "immutable" مهمًا للغاية ويخبرنا أن أي متغير في فئة عنصر واجهة المستخدم يجب أن يكون نهائيًا ، بمعنى آخر: "يتم تعريفه وتعيينه مرة واحدة لكل شخص ." وبالتالي ، بعد إنشاء مثيل ، لن تتمكن القطعة بعد الآن من تغيير متغيراتها الداخلية.


نظرًا لأن القطعة غير قابلة للتغيير ، يمكن اعتبارها تكوينًا ثابتًا.

الهيكل الهرمي للحاجيات


عندما تقوم بالتصميم باستخدام Flutter ، فإنك تحدد بنية شاشتك (شاشات) باستخدام عناصر واجهة مستخدم مثل هذا:


 Widget build(BuildContext context){ return SafeArea( child: Scaffold( appBar: AppBar( title: Text('My title'), ), body: Container( child: Center( child: Text('Centered Text'), ), ), ), ); } 

يستخدم هذا المثال 7 الحاجيات التي تشكل معا بنية هرمية. مخطط مبسط للغاية على أساس هذا الرمز هو كما يلي:



كما ترون ، فإن الرسم البياني المقدم يشبه شجرة ، حيث SafeArea هو جذرها.


الغابات وراء الأشجار


كما تعلمون بالفعل ، يمكن أن يكون عنصر واجهة المستخدم عبارة عن تجميع لعناصر واجهة المستخدم الأخرى. كمثال ، يمكنك تعديل الكود السابق كما يلي:


 Widget build(BuildContext context){ return MyOwnWidget(); } 

يفترض هذا الخيار أن الأداة "MyOwnWidget" نفسها ستعرض SafeArea ، سقالة . لكن الشيء الأكثر أهمية في هذا المثال هو ذلك


يمكن لعنصر واجهة المستخدم أن يمثل ورقة أو عقدة في شجرة ، أو حتى الشجرة نفسها ، أو لم لا ، غابة من الأشجار ...

فهم العنصر في شجرة


ما علاقة هذا به؟


كما سيظهر لاحقًا ، لكي تتمكن من إنشاء وحدات بكسل تشكل الصورة المعروضة على الجهاز ، يجب أن يعرف Flutter بالتفصيل جميع الأجزاء الصغيرة التي تتكون منها الشاشة ، ولتحديد جميع الأجزاء ، فإنه يحتاج إلى معرفة توسيع جميع عناصر واجهة المستخدم.


لتوضيح هذه النقطة ، ضع في اعتبارك مبدأ الدمية المتداخلة: عندما تغلق ، ترى دمية واحدة فقط ، ولكنها تحتوي على دمية أخرى ، والتي بدورها تحتوي على دمية أخرى وهكذا ...



عندما يوسع Flutter جميع الأدوات (جزء من الشاشة) ، سيكون الأمر مثل الحصول على جميع الدمى (جزء من الكل) .


تُظهر الصورة أدناه جزءًا من الهيكل الهرمي النهائي لعناصر الواجهة المطابقة للرمز السابق. باللون الأصفر ، قمت بتمييز عناصر واجهة المستخدم التي تم ذكرها في الكود سابقًا ، بحيث يمكنك تعريفها في الشجرة النهائية.



توضيح مهم

لغة "Widget tree" موجودة فقط لسهولة الفهم ، لأن المبرمجين يستخدمون عناصر واجهة تعامل المستخدم ، ولكن لا توجد شجرة عنصر واجهة مستخدم في Flutter!

في الواقع ، سيكون من الأصح قول "شجرة العناصر"

لقد حان الوقت لتقديم مفهوم العنصر .


كل عنصر واجهة مستخدم لديه عنصر واحد . ترتبط العناصر مع بعضها البعض وتشكل شجرة. لذلك ، العنصر هو إشارة إلى شيء في الشجرة.

بادئ ذي بدء ، فكر في عنصر كعقدة لها أصل وربما طفل. من خلال الربط بينهما من خلال علاقة الوالدين والطفل ، نحصل على بنية شجرة.



كما ترون ، يشير العنصر إلى عنصر واجهة مستخدم واحد ، ويمكن أن يشير أيضًا إلى RenderObject .


حتى أفضل ... يشير العنصر إلى عنصر واجهة المستخدم الذي أنشأ هذا العنصر!

لنلخص:


  • لا توجد شجرة عنصر واجهة مستخدم ، ولكن هناك شجرة عنصر
  • يتم إنشاء عناصر بواسطة الحاجيات.
  • يشير العنصر إلى عنصر واجهة المستخدم الذي قام بإنشائه.
  • العناصر المرتبطة بالعلاقات الأم
  • قد يكون عنصر "طفل".
  • يمكن أن تشير العناصر أيضًا إلى RenderObject.

تحدد العناصر كيفية ارتباط أجزاء الكتل المعروضة ببعضها البعض.

لكي نتخيل بشكل أفضل أين يتناسب مفهوم العنصر ، دعنا ننظر إلى التمثيل المرئي التالي:



كما ترى ، فإن شجرة العنصر هي العلاقة الفعلية بين عناصر واجهة التعامل و RenderObjects .


ولكن لماذا تقوم القطعة بإنشاء عنصر ؟


3 فئات من الحاجيات


في Flutter ، يتم تقسيم التطبيقات المصغّرة إلى 3 فئات ، وأنا أسميها شخصيًا على النحو التالي (ولكن هذه هي طريقتي فقط لتصنيفها) :


  • الوكيل


    تتمثل المهمة الرئيسية لهذه الأدوات المصغّرة في تخزين بعض المعلومات (التي يجب أن تكون في متناول الأدوات المصغّرة) ، وهي جزء من بنية الشجرة استنادًا إلى الوكيل. مثال على هذه الحاجيات هو InheritedWidget أو LayoutId .


    لا تشارك هذه التطبيقات المصغّرة بشكل مباشر في تكوين واجهة المستخدم ، ولكنها تستخدم للحصول على المعلومات التي يمكن أن توفرها.


  • العارض


    ترتبط هذه الحاجيات مباشرةً بتخطيط الشاشة ، لأنها تحدد (أو تستخدم لتحديد) الحجم والموضع والعرض . الأمثلة النموذجية هي: الصف ، العمود ، المكدس ، وكذلك الحشو ، المحاذاة ، العتامة ، RawImage ...


  • عنصر


    هذه هي الأدوات المصغرة الأخرى التي لا تقدم مباشرة المعلومات النهائية المتعلقة بالأحجام والمواقع والمظهر ، بل هي البيانات (أو النصائح) التي سيتم استخدامها للحصول على المعلومات النهائية للغاية. يشار إلى هذه الحاجيات عادة باسم المكونات.


    أمثلة: RaisedButton ، سقالة ، نص ، GestureDetector ، حاوية ...




يسرد ملف PDF معظم الأدوات المصنفة حسب الفئة.


لماذا هذا الفصل مهم؟ لأنه استنادًا إلى فئة عنصر واجهة المستخدم ، يرتبط نوع العنصر المقابل بـ ...


أنواع العناصر


هناك عدة أنواع من العناصر:



كما ترون في الصورة أعلاه ، تنقسم العناصر إلى نوعين رئيسيين:


  • ComponentElement


    هذه العناصر ليست مسؤولة بشكل مباشر عن تقديم أي جزء من الشاشة.


  • RenderObjectElement


    هذه العناصر مسؤولة عن أجزاء من الصورة المعروضة على الشاشة.



! ممتاز الكثير من المعلومات ، لكن ما علاقة كل هذا ببعضه البعض ولماذا من المثير للاهتمام التحدث عنه؟


كيف تعمل عناصر واجهة المستخدم الرسومية معًا


في Flutter ، تعتمد جميع الميكانيكا على إبطال عنصر أو تقديم renderObject.

يمكن إبطال العنصر بالطرق التالية:


  • باستخدام setState ، الذي يبطل StatefulElement بأكمله (لاحظ أنني لا أقول عن قصد StatefulWidget )
  • من خلال الإشعارات التي تتم معالجتها بواسطة proxyElement (على سبيل المثال ، InheritedWidget) ، والتي تبطل أي عنصر يعتمد على هذا proxyElement

نتيجة الإبطال هي ظهور رابط للعنصر المقابل في قائمة العناصر القذرة .


يعني إبطال renderObject أن بنية العناصر لا تتغير على الإطلاق ، ولكن هناك تغيير على مستوى renderObject ، على سبيل المثال:


  • تغيير حجمها ، والموقف ، والهندسة ...
  • يجب إعادة طلاء شيء ما ، على سبيل المثال ، عندما تقوم فقط بتغيير لون الخلفية ونمط الخط ...

نتيجة هذا الإبطال هي ارتباط إلى renderObject المطابق في قائمة كائنات العرض (renderObjects) التي تحتاج إلى إعادة بناء أو إعادة طلاؤها.


بغض النظر عن نوع الإبطال ، يسمى SchedulerBinding (تذكر هذا؟) لطلب محرك Flutter لجدولة إطار جديد.


هذه هي بالضبط اللحظة التي يستيقظ فيها " Flutter Engine " على " المجدول الزمني " ويحدث كل السحر ...


onDrawFrame ()


في وقت سابق من هذه المقالة ، لاحظنا أن SchedulerBinding يتحمل مسؤوليتين رئيسيتين ، أحدهما استعداده للتعامل مع الطلبات المقدمة من Flutter Engine المتعلقة بإعادة إنشاء الإطار. هذه هي اللحظة المثالية للتركيز على هذا.


يوضح الرسم البياني التسلسل الجزئي أدناه ما يحدث عندما يتلقى SchedulerBinding طلب onDrawFrame () من محرك Flutter .



الخطوة 1. العناصر


يسمى WidgetsBinding ، وهذا الربط يأخذ في الاعتبار التغييرات المرتبطة بالعناصر. يستدعي WidgetsBinding أسلوب buildScope الخاص بالكائن buildOwner ، لأن BuildOwner مسؤول عن معالجة شجرة العنصر. تمر هذه الطريقة بقائمة العناصر القذرة وتطلب إعادة بنائها .


المبادئ الرئيسية لأسلوب rebuild() ) هي:


  1. يوجد طلب لإعادة بناء العنصر (سيستغرق ذلك معظم الوقت) ، استدعاء الأسلوب build() لعنصر واجهة المستخدم الذي يشير إليه هذا العنصر (= أسلوب Widget build (BuildContext context) {...} ). ستُرجع طريقة build() عنصر واجهة مستخدم جديد
  2. إذا لم يكن للعنصر "أطفال" ، فسيتم إنشاء عنصر للعنصر الجديد (انظر أدناه) ( ملاحظة: inflateWidget ) ، وإلا
  3. تتم مقارنة عنصر واجهة تعامل المستخدم الجديد مع العنصر المشار إليه بواسطة عنصر العنصر
    • إذا كانت قابلة للتبديل (= نفس نوع عنصر واجهة المستخدم ومفتاحه ) ، فسيحدث التحديث ويتم حفظ الطفل.
    • إذا لم تكن قابلة للتبديل ، فسيتم تجاهل الطفل (تقريبًا) ويتم إنشاء عنصر لعنصر واجهة المستخدم الجديد
  4. يتم تثبيت هذا العنصر الجديد كطفل للعنصر. ( محمّل) = مدرج في شجرة العناصر)

ستحاول الرسوم المتحركة التالية جعل هذا الشرح أكثر وضوحًا.



ملاحظة على الحاجيات والعناصر


بالنسبة لعنصر واجهة تعامل مستخدم جديد ، يتم إنشاء عنصر من نوع معين يتوافق مع فئة عنصر واجهة تعامل المستخدم ، وهو:


  • InheritedWidget -> InheritedElement
  • StatefulWidget -> StatefulElement
  • StatelessWidget -> StatelessElement
  • InheritedModel -> InheritedModelElement
  • InheritedNotifier -> InheritedNotifierElement
  • LeafRenderObjectWidget -> LeafRenderObjectElement
  • SingleChildRenderObjectWidget -> SingleChildRenderObjectElement
  • MultiChildRenderObjectWidget -> MultiChildRenderObjectElement
  • ParentDataWidget -> ParentDataElement

كل من هذه الأنواع من العناصر لديها سلوكها الخاص. على سبيل المثال:


  • ستقوم StatefulElement باستدعاء طريقة widget.createState() عند التهيئة ، والتي ستنشئ حالة widget.createState() بالعنصر
  • عندما يتم تثبيت عنصر من النوع RenderObjectElement ، فإنه ينشئ RenderObject . ستتم إضافة renderObject هذه إلى Render Tree وترتبط بالعنصر.

الخطوة 2. renderObjects


الآن بعد الانتهاء من جميع الإجراءات المرتبطة بالعناصر القذرة ، فإن Element Tree مستقرة. لذلك حان الوقت للنظر في عملية التصور.


نظرًا لأن RendererBinding مسؤول عن تقديم Render Tree ، فإن WidgetsBinding تستدعي طريقة drawFrame RendererBinding .


يوضح الرسم التخطيطي الجزئي أدناه تسلسل الإجراءات المنفذة أثناء طلب drawFrame () .



في هذه الخطوة ، يتم تنفيذ الإجراءات التالية:


  • يُطلب من كل renderObject موسومة إنشاءها (أي حساب حجمها وهندستها)
  • يتم إعادة رسم كل renderObject التي تحمل علامة " need redrawing " باستخدام طريقة الطبقة الخاصة بها
  • يتم تكوين المشهد الناتج وإرساله إلى محرك Flutter ، بحيث يقوم الأخير بنقله إلى شاشة الجهاز
  • أخيرًا ، يتم أيضًا تحديث الدلالات وإرسالها إلى محرك Flutter

في نهاية سير العمل ، يتم تحديث شاشة الجهاز.


الجزء 3: التعامل مع الإيماءات


تتم معالجة الإيماءات (= الأحداث المتعلقة بالإصبع على الزجاج ) باستخدام GestureBinding .


عندما يرسل Flutter Engine معلومات حول حدث لفتة من خلال window.onPointerDataPacket API ، يقوم GestureBinding باعتراضه ، ويقوم ببعض التخزين المؤقت ، و:


  1. يحول الإحداثيات المقدمة من Flutter Engine لمطابقة نسبة بكسل الجهاز ، ثم
  2. يسترجع من renderView قائمة بجميع RenderObjects الموجودة في جزء من الشاشة المتعلقة بإحداثيات الحدث
  3. ثم يتكرر من خلال القائمة الناتجة من renderObjects ويرسل حدثًا ذا صلة إلى كل منهم
  4. إذا كان renderObject "يستمع" للأحداث من هذا النوع ، فإنه يعالجها

نأمل الآن أن أفهم مدى أهمية renderObjects .


الجزء 4: الرسوم المتحركة


هذا الجزء من المقالة يدور حول مفهوم الرسوم المتحركة وفهم عميق لل Ticker .


عند العمل مع الرسوم المتحركة ، عادة ما تستخدم AnimationController أو أي عنصر واجهة مستخدم للرسوم المتحركة ( ملاحظة: AnimatedCrossFade ).


في Flutter ، يشير كل ما يتعلق بالرسوم المتحركة إلى Ticker . عندما يكون النشاط نشطًا ، يكون لدى مهمة واحدة فقط: "يطلب من برنامج جدولة الربط تسجيل رد اتصال وإبلاغ محرك Flutter بتنبيهه عند ظهور رد اتصال جديد." عندما يكون محرك Flutter جاهزًا ، فإنه يستدعي جدولة الربط من خلال طلب: " onBeginFrame ". SchedulerBinding يصل إلى قائمة رد الاتصال شريط وتنفيذ كل واحدة.


يتم اعتراض كل علامة من قبل وحدة تحكم "مهتمة" لمعالجتها. إذا كانت الرسوم المتحركة كاملة ، فسيتم "تعطيل" المؤشر ، وإلا يطلب المؤشر جدولة ملزمة لجدولة رد اتصال جديد. وهلم جرا ...


الصورة كاملة


لقد تعلمنا الآن كيف يعمل الرفرفة :



BuildContext


أخيرًا ، عد إلى الرسم البياني الذي يعرض أنواع العناصر المختلفة ، والنظر في توقيع العنصر الجذر:


 abstract class Element extends DiagnosticableTree implements BuildContext { ... } 

نرى BuildContext الشهيرة جدا! لكن ما هذا؟


BuildContext هي واجهة تقوم بتعريف عدد من الأساليب والطرق التي يمكن تنفيذها بواسطة عنصر. في الغالب يتم استخدام BuildContext في أسلوب build() لـ StatelessWidget أو State for StatefulWidget .


BuildContext ليس سوى العنصر نفسه ، والذي يطابق
  • يتم تحديث عنصر واجهة المستخدم (داخل أساليب builder أو builder )
  • StatefulWidget المقترن بالحالة التي تشير فيها إلى متغير السياق.

هذا يعني أن معظم المطورين يعملون باستمرار مع العناصر دون حتى معرفة ذلك.


كيف يمكن أن يكون BuildContext؟


BuildContext , , , BuildContext , :


  • RenderObject , (, Renderer , -)
  • RenderObject
  • . , of (, MediaQuery.of(context) , Theme.of(context) …)


, , BuildContext, . StatelessWidget , StatefulWidget , setState() , BuildContext .



, !

– , StatelessWidget .
, , StatefulWidget .

 void main(){ runApp(MaterialApp(home: TestPage(),)); } class TestPage extends StatelessWidget { // final because a Widget is immutable (remember?) final bag = {"first": true}; @override Widget build(BuildContext context){ return Scaffold( appBar: AppBar(title: Text('Stateless ??')), body: Container( child: Center( child: GestureDetector( child: Container( width: 50.0, height: 50.0, color: bag["first"] ? Colors.red : Colors.blue, ), onTap: (){ bag["first"] = !bag["first"]; // // This is the trick // (context as Element).markNeedsBuild(); } ), ), ), ); } } 

, setState() , : _element.markNeedsBuild() .


استنتاج


: " ". , , Flutter , , , , . , , Widget , Element , BuildContext , RenderObject , . , .


. .


PS , () .
PSS Flutter internals Didier Boelens, )

Source: https://habr.com/ru/post/ar476018/


All Articles