
Wie funktioniert Flutter eigentlich?
Was sind Widgets, Elemente, BuildContext, RenderOject, Bindungen? ..
Schwierigkeit: Anfänger
Eintrag
Letztes Jahr ( Anmerkung: 2018 ), als ich meine Reise in die fabelhafte Welt von Flutter antrat, gab es im Internet nur sehr wenige Informationen im Vergleich zu dem, was es heute ist. Trotz der Tatsache, dass bereits viele Materialien geschrieben wurden, spricht nur ein kleiner Teil davon darüber, wie Flutter tatsächlich funktioniert.
Was sind Widgets ( Widgets ), Elemente ( Elemente ), BuildContext? Warum flattert es so schnell? Warum funktioniert es manchmal nicht wie erwartet? Was sind Bäume und warum werden sie benötigt?
In 95% der Fälle beschäftigen Sie sich beim Schreiben einer Anwendung nur mit Widgets, um etwas anzuzeigen oder damit zu interagieren. Aber haben Sie sich nie wirklich gefragt, wie all diese Magie in Ihnen wirkt? Woher weiß das System, wann der Bildschirm aktualisiert werden muss und welche Teile aktualisiert werden müssen?
Inhalt:
Teil 1: Hintergrund
Der erste Teil des Artikels stellt einige Schlüsselkonzepte vor, die im zweiten Teil des Materials verwendet werden und zum besseren Verständnis von Flutter beitragen.
Ein bisschen über das Gerät
Beginnen wir am Ende und kehren zu den Grundlagen zurück.
Wenn Sie sich Ihr Gerät oder genauer gesagt die Anwendung ansehen, die auf Ihrem Gerät ausgeführt wird, sehen Sie nur den Bildschirm.
In der Tat sehen Sie nur die Pixel, die zusammen ein zweidimensionales Bild ergeben. Wenn Sie den Bildschirm mit dem Finger berühren, erkennt das Gerät nur die Position Ihres Fingers auf dem Glas.
In den meisten Fällen besteht die Magie der Anwendung (visuell gesehen) darin, dieses Bild auf der Grundlage der folgenden Interaktionen zu aktualisieren:
- mit dem Gerätebildschirm ( zum Beispiel ein Finger auf dem Glas )
- mit dem Netzwerk ( z. B. Kommunikation mit dem Server )
- im Laufe der Zeit ( z. B. Animation )
- mit anderen externen Sensoren
Die Visualisierung des Bildes auf dem Bildschirm erfolgt durch Hardware (Display), die die Anzeige regelmäßig (in der Regel 60 Mal pro Sekunde) aktualisiert. Dies wird als "Bildwiederholfrequenz" bezeichnet und in Hz (Hertz) ausgedrückt.
Das Display erhält Informationen zur Anzeige von der GPU (Graphics Processing Unit), einer speziellen elektronischen Schaltung, die optimiert und so konzipiert ist, dass aus einigen Daten (Polygonen und Texturen) schnell Bilder erzeugt werden. Die Anzahl der Male pro Sekunde, die der Grafikprozessor ein "Bild" (= Bildpuffer) erzeugen kann, um es anzuzeigen und an die Hardware zu senden, wird als Bildrate bezeichnet ( Hinweis: Bildrate ). Dies wird unter Verwendung eines Blocks von Bildern pro Sekunde ( z. B. 60 Bilder pro Sekunde oder 60 fps ) gemessen .
Sie mögen mich fragen, warum ich diesen Artikel mit den Konzepten eines zweidimensionalen Bildes begonnen habe, das von einer GPU / Hardware und einem physischen Glassensor angezeigt wird, und wie ist der Zusammenhang mit normalen Flutter-Widgets?
Ich denke, es wird einfacher zu verstehen sein, wie Flutter tatsächlich funktioniert, wenn wir es aus dieser Sicht betrachten, da eines der Hauptziele der Flutter-Anwendung darin besteht, dieses zweidimensionale Bild zu erstellen und es ihm zu ermöglichen, damit zu interagieren. Auch weil in Flutter, ob Sie es glauben oder nicht, ist fast alles auf die Notwendigkeit zurückzuführen, den Bildschirm schnell und zum richtigen Zeitpunkt zu aktualisieren!
Schnittstelle zwischen Code und Gerät
Jedenfalls hat jeder, der sich für Flutter interessiert, bereits das folgende Bild gesehen, das Flutters High-Level-Architektur beschreibt .

Wenn wir eine Flutter-Anwendung mit Dart schreiben, bleiben wir auf der Ebene des Flutter-Frameworks (grün hervorgehoben).
Das Flutter Framework interagiert mit der Flutter Engine (blau) über eine Abstraktionsebene namens Window . Diese Abstraktionsebene bietet eine Reihe von APIs für die indirekte Interaktion mit dem Gerät.
Durch diese Abstraktionsebene benachrichtigt die Flutter Engine das Flutter Framework, wenn:
- Ein Ereignis von Interesse tritt auf Geräteebene auf (Änderung der Ausrichtung, Änderung der Einstellungen, Speicherproblem, Betriebszustand der Anwendung ...)
- Ein Ereignis tritt auf der Glasebene auf (= Geste)
- Plattformkanal sendet einige Daten
- aber auch hauptsächlich, wenn die Flutter Engine bereit ist, einen neuen Frame zu rendern
Verwalten des Flutter Framework Flutter Engine-Renderings
Es ist schwer zu glauben, aber es ist wahr. Mit Ausnahme einiger Fälle ( siehe unten ) wird kein Flutter Framework- Code ausgeführt, ohne das Flutter Engine- Rendering zu starten.
Ausnahmen:
- Geste / Geste (= Ereignis auf Glas)
- Plattformnachrichten (= von einem Gerät generierte Nachrichten, z. B. GPS)
- Gerätemeldungen (= Meldungen, die sich auf eine Änderung des Gerätestatus beziehen, z. B. Ausrichtung, im Hintergrund gesendete Anwendung, Speicherwarnungen, Geräteeinstellungen ...)
- Zukünftige oder http-Antworten
(Zwischen uns können Sie tatsächlich eine visuelle Änderung vornehmen, ohne von der Flutter Engine aus aufzurufen. Dies wird jedoch nicht empfohlen. )
Sie fragen mich: "Wenn ein Code im Zusammenhang mit der Geste ausgeführt wird und eine visuelle Änderung verursacht, oder wenn ich einen Timer verwende , um die Häufigkeit der Aufgabe festzulegen, die zu visuellen Änderungen führt (z. B. Animation), wie funktioniert das?"
Wenn Sie möchten, dass eine visuelle Änderung auftritt oder ein Code basierend auf einem Timer ausgeführt wird, müssen Sie der Flutter-Engine mitteilen, dass etwas gezeichnet werden muss.
Normalerweise ruft die Flutter Engine bei der nächsten Aktualisierung das Flutter Framework auf, um Code auszuführen, und stellt schließlich eine neue Szene zum Rendern bereit.
Daher ist eine wichtige Frage, wie die Flutter-Engine das gesamte Anwendungsverhalten basierend auf dem Rendern organisiert.
Sehen Sie sich die folgende Animation an, um sich ein Bild von den internen Mechanismen zu machen:

Eine kurze Erklärung (weitere Details folgen später):
- Einige externe Ereignisse (Gesten, HTTP-Antworten usw.) oder sogar Zukünfte können Aufgaben auslösen, die eine Aktualisierung der Anzeige erforderlich machen. Die entsprechende Nachricht wird an die Flutter Engine gesendet (= Schedule Frame )
- Wenn die Flutter Engine bereit ist, das Rendering zu aktualisieren, erstellt sie eine Anforderung für den Frame- Beginn
- Diese Anforderung für Frame-Beginn wird vom Flutter Framework abgefangen, das Aufgaben ausführt, die hauptsächlich mit Tickern zusammenhängen (z. B. Animation).
- Diese Tasks können die Anforderung für ein späteres Rendern neu erstellen (Beispiel: Die Animation wurde noch nicht ausgeführt, und zum Abschluss muss zu einem späteren Zeitpunkt ein weiterer Startframe abgerufen werden. )
- Als Nächstes sendet die Flutter Engine einen Draw Frame , der vom Flutter Framework abgefangen wird und nach Aufgaben sucht, die mit der Aktualisierung des Layouts in Bezug auf Struktur und Größe zusammenhängen
- Nachdem alle diese Aufgaben erledigt sind, fährt er mit den Aufgaben fort, die mit der Aktualisierung des Layouts in Bezug auf das Rendern verbunden sind
- Wenn auf dem Bildschirm etwas gezeichnet werden muss, wird eine neue Szene ( Scene ) zur Visualisierung an die Flutter Engine gesendet, die den Bildschirm aktualisiert
- Das Flutter Framework führt dann alle Aufgaben aus, die nach dem Rendern ausgeführt werden (= PostFrame-Rückrufe), sowie alle weiteren nachfolgenden Aufgaben, die sich nicht auf das Rendern beziehen
- ... und dieser Prozess beginnt von vorne
RenderView und RenderObject
Bevor Sie sich mit den Details des Workflows befassen, ist es Zeit, das Konzept des Rendering-Baums vorzustellen.
Wie bereits erwähnt, wird irgendwann alles in Pixel konvertiert, die auf dem Bildschirm angezeigt werden, und das Flutter Framework konvertiert die Widgets , mit denen wir die Anwendung entwickeln, in visuelle Blöcke, die auf dem Bildschirm angezeigt werden.
Diese visuellen Teile entsprechen Objekten mit dem Namen RenderObject , die verwendet werden, um:
- Definieren eines bestimmten Bereichs des Bildschirms in Bezug auf Größe, Position, Geometrie sowie in Bezug auf "gerenderten Inhalt"
- Bereiche des Bildschirms identifizieren, die von Gesten betroffen sein können (= Fingerberührung)
Ein Satz aller RenderObjects bildet einen Baum, der als Render Tree bezeichnet wird . Oben in diesem Baum (= root ) befindet sich eine RenderView .
RenderView bietet eine gemeinsame Oberfläche für Render Tree- Objekte und ist eine spezielle Version von RenderObject .
Optisch könnten wir das alles wie folgt darstellen:

Die Beziehung zwischen Widget und RenderObject wird später erläutert. In der Zwischenzeit ist es Zeit, etwas tiefer zu gehen ...
Initialisierungsbindungen
Wenn die Flutter-Anwendung runApp(Widget app)
wird, wird zuerst die main()
-Funktion aufgerufen, die letztendlich die runApp(Widget app)
Methode runApp(Widget app)
.
Wenn die runApp()
-Methode runApp()
initialisiert das Flutter Framework die Schnittstellen zwischen sich und der Flutter Engine . Diese Schnittstellen werden Bindungen genannt ( Anmerkung: Bindungen ).
Einführung in Bindungen
Bindungen sind als Bindeglied zwischen dem Framework und der Flutter-Engine konzipiert. Nur durch Bindungen können Daten zwischen dem Flutter Framework und der Flutter Engine ausgetauscht werden.
(Es gibt nur eine Ausnahme von dieser Regel - RenderView , aber wir werden dies später diskutieren).
Jede Bindung ist für die Verarbeitung einer Reihe spezifischer Aufgaben, Aktionen und Ereignisse verantwortlich, die nach Tätigkeitsbereichen gruppiert sind.
Zum Zeitpunkt des Schreibens dieses Dokuments verfügt das Flutter Framework über 8 Bindungen.
Im Folgenden sind 4 davon aufgeführt, die in diesem Artikel behandelt werden:
- SchedulerBinding
- Gestenbindung
- Renderer-Bindung
- Widgets verbindlich
Der Vollständigkeit halber erwähne ich die restlichen 4:
- ServicesBinding : Verantwortlich für die Verarbeitung der vom Plattformkanal gesendeten Nachrichten
- PaintingBinding : Verantwortlich für die Verarbeitung des Bild-Cache
- SemanticsBinding : reserviert für die spätere Implementierung von allem, was mit Semantik zu tun hat
- TestWidgetsFlutterBinding : Wird von der Widget- Testbibliothek verwendet
Sie können auch WidgetsFlutterBinding erwähnen, dies ist jedoch nicht wirklich eine Bindung, sondern eher eine Art "Bindungsinitialisierer".
Das folgende Diagramm zeigt die Interaktion zwischen den Bindungen, die ich als Nächstes betrachten werde, und der Flutter-Engine .

Schauen wir uns jede dieser „Kern“ -Bindungen an.
SchedulerBinding
Diese Bindung hat zwei Hauptaufgaben:
- Sagen Sie " Flutter Engine " : "Hey! Wenn Sie das nächste Mal nicht beschäftigt sind, wecken Sie mich auf, damit ich ein bisschen arbeiten und Ihnen sagen kann, was Sie rendern sollen, oder wenn Sie mich später anrufen müssen ..."
- Hören Sie zu und reagieren Sie auf solche „störenden Erwachen“ (siehe unten)
Wann fordert SchedulerBinding einen Weckruf an ?
Wann muss der Ticker einen neuen Tick ausarbeiten ?
Wenn Sie beispielsweise eine Animation haben, starten Sie diese. Die Animation wird mit dem Ticker beschnitten, der in regelmäßigen Abständen (= Tick ) aufgerufen wird, um einen Callback durchzuführen. Um einen solchen Callback zu starten, müssen wir der Flutter Engine mitteilen, dass sie uns beim nächsten Update (= Begin Frame ) aufweckt. Dadurch wird der Ticker- Rückruf gestartet, um seine Aufgabe abzuschließen. Wenn der Ticker weiterhin ausgeführt werden muss, ruft er am Ende seiner Aufgabe SchedulerBinding auf, um einen weiteren Frame zu planen.
Wann muss die Anzeige aktualisiert werden?
Beispielsweise müssen wir ein Ereignis ausarbeiten, das zu einer visuellen Änderung führt (Beispiel: Aktualisieren der Farbe eines Teils des Bildschirms, Scrollen, Hinzufügen / Entfernen von Elementen vom Bildschirm). Dazu müssen wir die erforderlichen Schritte ausführen, um das aktualisierte Bild schließlich auf dem Bildschirm anzuzeigen. In diesem Fall ruft das Flutter Framework bei einer solchen Änderung SchedulerBinding auf, um mithilfe der Flutter Engine einen anderen Frame zu planen. (Später werden wir sehen, wie das tatsächlich funktioniert)
Gestenbindung
Diese Bindung lauscht der Interaktion mit der Engine im Sinne des „Fingers“ (= Geste ).
Insbesondere ist er dafür verantwortlich, fingerbezogene Daten zu empfangen und zu bestimmen, mit welchem Teil des Bildschirms die Gesten arbeiten. Er teilt diese Teile dann entsprechend mit.
Renderer-Bindung
Diese Bindung ist die Verbindung zwischen der Flutter-Engine und dem Render-Baum . Sie ist verantwortlich für:
- Abhören von Ereignissen, die von der Engine generiert wurden, um über vom Benutzer durch Geräteeinstellungen vorgenommene Änderungen zu informieren, die sich auf visuelle Effekte und / oder Semantik auswirken
- Meldung an die Engine über Änderungen, die auf die Anzeige angewendet werden
Damit die Änderungen auf dem Bildschirm angezeigt werden, ist RendererBinding für die Verwaltung des PipelineOwners und die Initialisierung der RenderView verantwortlich .
PipelineOwner ist eine Art Orchester , das weiß, was mit RenderObject entsprechend der Komponente zu tun ist, und diese Aktionen koordiniert.
Diese Bindung wartet auf Änderungen, die vom Benutzer über Geräteeinstellungen vorgenommen wurden, die sich auf die Sprache (= Gebietsschema ) und die Semantik auswirken.
Kleine Notiz
Ich gehe davon aus, dass zu einem späteren Zeitpunkt in der Entwicklung von Flutter alle semantikbezogenen Ereignisse in das SemanticsBinding übertragen werden , aber zum Zeitpunkt dieses Schreibens ist dies nicht der Fall.
Darüber hinaus ist WidgetsBinding die Verbindung zwischen Widgets und der Flutter Engine . Sie ist verantwortlich für:
- Verwaltung des Prozesses der Verarbeitung von Widget-Strukturänderungen
- Anruf tätigen
Die Bearbeitung von Änderungen an der Struktur von Widgets erfolgt mit BuildOwner .
BuildOwner verfolgt, welche Widgets neu erstellt werden müssen, und verarbeitet andere Aufgaben, die für die Widgetstruktur insgesamt gelten.
Teil 2. Von Widgets zu Pixeln
Nachdem wir nun die Grundlagen der internen Arbeit von Flutter kennen gelernt haben, ist es Zeit, über Widgets zu sprechen.
In allen Flutter-Dokumentationen lesen Sie, dass alle Widgets (Widgets).
Das ist fast richtig. Aber um ein bisschen genauer zu sein, würde ich eher sagen:
Auf Entwicklerseite erfolgt alles, was in Bezug auf Layout und Interaktion mit der Benutzeroberfläche zu tun hat, über Widgets.
Warum so viel Genauigkeit? Neben der Tatsache, dass Widget es dem Entwickler ermöglicht, einen Teil des Bildschirms in Bezug auf Größe, Inhalt, Layout und Interaktion zu bestimmen, steckt jedoch noch viel mehr dahinter. Also, was ist Widget wirklich?
Unveränderliche Konfiguration
Wenn Sie sich den Quellcode von Flutter ansehen, werden Sie die folgende Definition der Widget- Klasse bemerken.
@immutable abstract class Widget extends DiagnosticableTree { const Widget({ this.key }); final Key key; ... }
Was bedeutet das?
Die Anmerkung "@immutable" ist sehr wichtig und besagt, dass jede Variable in der Widget-Klasse FINAL sein muss , mit anderen Worten: " EINMAL FÜR JEDEN definiert und zugewiesen." Nach dem Erstellen einer Instanz kann Widget daher seine internen Variablen nicht mehr ändern.
Da Widget unveränderlich ist, kann es als statische Konfiguration betrachtet werden.
Die hierarchische Struktur von Widgets
Wenn Sie mit Flutter entwerfen, definieren Sie die Struktur Ihres Bildschirms (Ihrer Bildschirme) mit Widgets wie folgt:
Widget build(BuildContext context){ return SafeArea( child: Scaffold( appBar: AppBar( title: Text('My title'), ), body: Container( child: Center( child: Text('Centered Text'), ), ), ), ); }
In diesem Beispiel werden 7 Widgets verwendet, die zusammen eine hierarchische Struktur bilden. Ein sehr vereinfachtes Schema, das auf diesem Code basiert, lautet wie folgt:

Wie Sie sehen, sieht das dargestellte Diagramm wie ein Baum aus, in dem SafeArea die Wurzel ist.
Wald hinter den Bäumen
Wie Sie bereits wissen, kann ein Widget selbst eine Zusammenfassung anderer Widgets sein. Beispielsweise können Sie den vorherigen Code wie folgt ändern:
Widget build(BuildContext context){ return MyOwnWidget(); }
Diese Option setzt voraus, dass das Widget "MyOwnWidget" selbst SafeArea , Scaffold anzeigt . Aber das Wichtigste in diesem Beispiel ist das
Ein Widget kann ein Blatt, einen Knoten in einem Baum, sogar den Baum selbst oder, warum nicht, einen Wald von Bäumen darstellen ...
Grundlegendes zum Element in einem Baum
Was hat das damit zu tun?
Wie später noch gezeigt wird, muss Flutter alle kleinen Teile des Bildschirms im Detail kennen, um Pixel für das auf dem Gerät angezeigte Bild erzeugen zu können, und um alle Teile zu bestimmen, muss es die Erweiterung aller Widgets kennen.
Um diesen Punkt zu veranschaulichen, betrachten Sie das Prinzip einer verschachtelten Puppe: Wenn sie geschlossen ist, sehen Sie nur eine Puppe, aber sie enthält eine andere, die wiederum eine andere enthält und so weiter ...

Wenn Flutter alle Widgets (Teil des Bildschirms) erweitert , werden alle Puppen (Teil des Ganzen) abgerufen .
Das folgende Bild zeigt einen Teil der endgültigen hierarchischen Struktur von Widgets, die dem vorherigen Code entsprechen. In Gelb habe ich die zuvor im Code erwähnten Widgets hervorgehoben, damit Sie sie im endgültigen Baum definieren können.

Wichtige Klarstellung
Die Sprache "Widget-Baum" existiert nur, um das Verständnis zu erleichtern, da Programmierer Widgets verwenden, aber es gibt KEINEN Widget-Baum in Flutter!
Tatsächlich wäre es richtiger, "Baum der Elemente" zu sagen.
Es ist Zeit, das Konzept eines Elements einzuführen.
Jedes Widget hat ein Element. Elemente sind miteinander verbunden und bilden einen Baum. Daher ist ein Element eine Referenz auf etwas im Baum.
Stellen Sie sich ein Element zunächst als Knoten vor, der ein übergeordnetes und möglicherweise ein untergeordnetes Element hat. Indem wir sie durch eine Eltern-Kind- Beziehung miteinander verbinden, erhalten wir eine Baumstruktur.

Wie Sie sehen, zeigt das Element auf ein Widget und kann auch auf ein RenderObject zeigen .
Noch besser ... Element zeigt auf Widget, das dieses Element erstellt hat!
Fassen wir zusammen:
- Es gibt keinen Widgetbaum, aber einen Elementbaum
- Elemente werden von Widgets erstellt.
- Das Element bezieht sich auf das Widget, das es erstellt hat.
- Elemente, die mit übergeordneten Beziehungen verknüpft sind
- Ein Gegenstand kann ein "Baby" haben.
- Elemente können auch auf ein RenderObject zeigen.
Elemente bestimmen, wie Teile der angezeigten Blöcke miteinander in Beziehung stehen.
Um sich besser vorstellen zu können, wo das Konzept eines Elements passt, schauen wir uns die folgende visuelle Darstellung an:

Wie Sie sehen, ist der Elementbaum die tatsächliche Beziehung zwischen Widgets und RenderObjects .
Aber warum erstellt Widget ein Element ?
3 Kategorien von Widgets
In Flutter sind Widgets in drei Kategorien unterteilt, die ich persönlich wie folgt nenne (dies ist jedoch nur meine Art, sie zu klassifizieren) :
Proxy
Die Hauptaufgabe dieser Widgets besteht darin, einige Informationen zu speichern (auf die Widgets zugreifen können sollten), die Teil der auf Proxy basierenden Baumstruktur sind. Ein Beispiel für solche Widgets ist InheritedWidget oder LayoutId .
Diese Widgets sind nicht direkt an der Gestaltung der Benutzeroberfläche beteiligt, sondern werden verwendet, um die Informationen abzurufen, die sie bereitstellen können.
Renderer
Diese Widgets stehen in direktem Zusammenhang mit dem Layout des Bildschirms, da sie die Größe , Position und das Rendering bestimmen (oder dazu verwendet werden). Typische Beispiele sind: Row , Column , Stack sowie Padding , Align , Opacity , RawImage ...
Komponente
Hierbei handelt es sich um andere Widgets, die nicht direkt die endgültigen Informationen zu Größe, Position und Erscheinungsbild bereitstellen, sondern vielmehr die Daten (oder Tipps), mit denen die endgültigen Informationen abgerufen werden. Diese Widgets werden üblicherweise als Komponenten bezeichnet.
Beispiele: RaisedButton , Scaffold , Text , GestureDetector , Container ...

In dieser PDF-Datei sind die meisten Widgets nach Kategorien gruppiert.
Warum ist diese Trennung wichtig? Weil abhängig von der Kategorie des Widgets der entsprechende Elementtyp mit ...
Artikeltypen
Es gibt verschiedene Arten von Elementen:

Wie Sie im obigen Bild sehen können, sind die Elemente in zwei Haupttypen unterteilt:
Großartig! So viele Informationen, aber wie hängt das alles zusammen und warum ist es interessant, darüber zu sprechen?
Wie Widgets und Elemente zusammenarbeiten
In Flutter basieren alle Mechaniken auf der Ungültigmachung eines Elements oder Renderobjekts.
Die Ungültigmachung von Elementen kann auf folgende Arten erfolgen:
- Verwenden von
setState
, wodurch das gesamte StatefulElement ungültig wird (beachte, dass ich StatefulWidget absichtlich nicht sage) - durch Benachrichtigungen, die von proxyElement verarbeitet werden (z. B. InheritedWidget), wodurch jedes Element ungültig wird , das von diesem proxyElement abhängt
Das Ergebnis der Ungültigkeit ist, dass in der Liste der fehlerhaften Elemente ein Link zu dem entsprechenden Element angezeigt wird.
Die Ungültigkeit von renderObject bedeutet, dass sich die Struktur der Elemente überhaupt nicht ändert, aber es gibt eine Änderung auf der Ebene von renderObject , zum Beispiel:
- Änderung seiner Größe, Position, Geometrie ...
- Etwas muss neu gestrichen werden, zum Beispiel, wenn Sie nur die Hintergrundfarbe, den Schriftstil ... ändern.
Das Ergebnis einer solchen Invalidierung ist eine Verknüpfung zu dem entsprechenden RenderObject in der Liste der Renderobjekte (RenderObjects) , die neu erstellt oder überarbeitet werden müssen.
Unabhängig von der Art der Invalidierung wird SchedulerBinding aufgerufen (nicht vergessen?), Um die Flutter Engine aufzufordern , einen neuen Frame zu planen.
Dies ist genau der Moment, in dem die Flutter Engine das SchedulerBinding "aufweckt" und all die Magie passiert ...
onDrawFrame ()
Weiter oben in diesem Artikel haben wir festgestellt, dass SchedulerBinding zwei Hauptaufgaben hat, von denen eine die Bereitschaft ist, Anforderungen von Flutter Engine im Zusammenhang mit der Frame-Neuerstellung zu verarbeiten. Dies ist der perfekte Moment, um sich darauf zu konzentrieren.
Das folgende Teilsequenzdiagramm zeigt, was passiert, wenn SchedulerBinding eine onDrawFrame () - Anforderung von der Flutter Engine empfängt.

Schritt 1. Elemente
WidgetsBinding wird aufgerufen und diese Bindung berücksichtigt zuerst die mit den Elementen verbundenen Änderungen. WidgetsBinding ruft die buildScope- Methode des buildOwner- Objekts auf, da BuildOwner für die Verarbeitung des Elementbaums verantwortlich ist. Diese Methode durchsucht die Liste der fehlerhaften Elemente und fordert deren Neuerstellung an .
Die Hauptprinzipien dieser rebuild()
( rebuild()
) sind:
- Es besteht eine Anforderung, das Element neu zu
build()
(dies wird die meiste Zeit in Widget build (BuildContext context) {...}
nehmen), wobei die build()
-Methode des Widgets Widget build (BuildContext context) {...}
, auf das sich dieses Element bezieht (= Widget build (BuildContext context) {...}
-Methode). Diese build()
-Methode gibt ein neues Widget zurück - Wenn das Element keine untergeordneten Elemente hat, wird ein Element für das neue Widget erstellt (siehe unten) ( Hinweis: inflateWidget ), andernfalls
- Das neue Widget wird mit dem Widget verglichen, auf das das untergeordnete Element des Elements verweist
- Wenn sie austauschbar sind (= derselbe Widget-Typ und -Schlüssel ), erfolgt die Aktualisierung und das untergeordnete Element wird gespeichert.
- Wenn sie nicht austauschbar sind, wird das untergeordnete Element verworfen ( ~ verworfen ) und ein Element für das neue Widget erstellt
- Dieses neue Element wird als untergeordnetes Element des Elements bereitgestellt. ( montiert) = in den Elementbaum eingefügt)
Die folgende Animation soll diese Erklärung etwas klarer machen.

Hinweis zu Widgets und Elementen
Für ein neues Widget wird ein Element eines bestimmten Typs erstellt, das der Widget- Kategorie entspricht , nämlich:
- InheritedWidget -> InheritedElement
- StatefulWidget -> StatefulElement
- StatelessWidget -> StatelessElement
- InheritedModel -> InheritedModelElement
- InheritedNotifier -> InheritedNotifierElement
- LeafRenderObjectWidget -> LeafRenderObjectElement
- SingleChildRenderObjectWidget -> SingleChildRenderObjectElement
- MultiChildRenderObjectWidget -> MultiChildRenderObjectElement
- ParentDataWidget -> ParentDataElement
Jeder dieser Elementtypen hat sein eigenes Verhalten. Zum Beispiel:
- StatefulElement ruft bei der Initialisierung die Methode
widget.createState()
auf, die einen widget.createState()
erstellt und mit dem Element verknüpft - Wenn ein Element vom Typ RenderObjectElement angehängt wird, wird ein RenderObject erstellt . Dieses renderObject wird dem Render Tree hinzugefügt und dem Element zugeordnet.
Schritt 2. renderObjects
Nachdem Sie nun alle mit schmutzigen Elementen verbundenen Aktionen ausgeführt haben, ist der Elementbaum stabil. Es ist also Zeit, den Visualisierungsprozess zu betrachten.
Da RendererBinding für das Rendern des Renderbaums verantwortlich ist, ruft drawFrame
die drawFrame
RendererBinding- Methode auf.
Das folgende Teildiagramm zeigt die Reihenfolge der Aktionen, die während der drawFrame () - Anforderung ausgeführt wurden.

In diesem Schritt werden die folgenden Aktionen ausgeführt:
- Jedes als unsauber markierte Renderobjekt wird aufgefordert, es zu komponieren (d. H. Seine Größe und Geometrie zu berechnen).
- Jedes RenderObject, das als "neu zu zeichnen" markiert ist, wird mit seiner eigenen Ebenenmethode neu gezeichnet
- Die resultierende Szene wird gebildet und an die Flutter Engine gesendet, damit diese sie auf den Gerätebildschirm überträgt
- Schließlich wird auch die Semantik aktualisiert und an die Flutter Engine gesendet
Am Ende dieses Workflows wird der Gerätebildschirm aktualisiert.
Teil 3: Umgang mit Gesten
Gesten (= Ereignisse im Zusammenhang mit Fingeraktionen auf dem Glas ) werden mit GestureBinding verarbeitet.
Wenn die Flutter-Engine Informationen zu einem Gestenereignis über die window.onPointerDataPacket- API sendet, fängt sie die GestureBinding ab , führt eine Pufferung durch und führt Folgendes aus :
- konvertiert die von der Flutter Engine angegebenen Koordinaten so, dass sie dem Pixelverhältnis des Geräts entsprechen
- Ruft aus renderView eine Liste aller RenderObjects ab , die sich in dem Teil des Bildschirms befinden, der sich auf die Koordinaten des Ereignisses bezieht
- Durchläuft dann die resultierende Liste von renderObjects und sendet ein zugehöriges Ereignis an jedes von ihnen
- Wenn renderObject Ereignisse dieses Typs "abhört", verarbeitet es sie
Hoffentlich verstehe ich jetzt, wie wichtig renderObjects ist .
Teil 4: Animationen
In diesem Teil des Artikels geht es um das Konzept der Animation und ein tiefes Verständnis des Tickers .
Wenn Sie mit Animationen arbeiten, verwenden Sie normalerweise einen AnimationController oder ein anderes Widget für Animationen ( Hinweis: AnimatedCrossFade ).
In Flutter bezieht sich alles , was mit Animationen zu tun hat, auf den Ticker . Wenn der Ticker aktiv ist, hat er nur eine Aufgabe: "Er fordert SchedulerBinding auf , einen Rückruf zu registrieren und die Flutter Engine anzuweisen, ihn zu aktivieren, wenn ein neuer Rückruf angezeigt wird." Wenn die Flutter Engine bereit ist, ruft sie SchedulerBinding über eine Anforderung auf: " onBeginFrame ". SchedulerBinding greift auf die Ticker- Rückrufliste zu und führt jede aus.
Jeder Tick wird von einem "interessierten" Controller abgefangen, um ihn zu verarbeiten. Wenn die Animation abgeschlossen ist, ist der Ticker deaktiviert, andernfalls fordert der Ticker eine SchedulerBinding auf, einen neuen Rückruf zu planen. Usw...
Vollbild
Jetzt haben wir gelernt, wie Flutter funktioniert:

Buildcontext
Kehren Sie schließlich zu dem Diagramm zurück, das die verschiedenen Elementtypen zeigt, und betrachten Sie die Signatur des Stammelements :
abstract class Element extends DiagnosticableTree implements BuildContext { ... }
Wir sehen den sehr berühmten BuildContext ! Aber was ist es
BuildContext ist eine Schnittstelle, die eine Reihe von Gettern und Methoden definiert, die von einem Element implementiert werden können. Meist wird BuildContext in der build()
-Methode von StatelessWidget oder State für StatefulWidget verwendet .
BuildContext ist nichts anderes als das Element selbst, das übereinstimmt
- Widget wird aktualisiert (innerhalb der
build
oder builder
Methoden) - StatefulWidget, das dem Status zugeordnet ist, in dem Sie auf die Kontextvariable verweisen.
Dies bedeutet, dass die meisten Entwickler ständig mit Elementen arbeiten, ohne es zu wissen.
Wie nützlich kann ein BuildContext sein?
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 {
, setState()
, : _element.markNeedsBuild()
.
Fazit
: " ". , , Flutter , , , , . , , Widget , Element , BuildContext , RenderObject , . , .
. .
PS , () .
PSS Flutter internals Didier Boelens, )