Flutter, hvad er Widgets, RenderObjects og Elements?

Har du nogensinde spekuleret på, hvordan Flutter tager disse widgets og faktisk konverterer dem til pixels på skærmen?

Foto af Shane Aldendorff på Unsplash

Har du nogensinde spekuleret på, hvordan Flutter tager disse widgets og faktisk konverterer dem til pixels på skærmen? Ingen?

Du burde!

At forstå, hvordan en underliggende teknologi fungerer, gør forskellen mellem en god udvikler og en stor en.

Du kan lettere oprette tilpassede layouts og specialeffekter, når du ved, hvad der fungerer, og hvad der ikke gør; og at vide at dette sparer dig et par lange nætter på tastaturet.

Målet med dette indlæg er at introducere dig til verden uden for fladen. Vi vil se på forskellige aspekter af fladder og forstå, hvordan det rent faktisk fungerer.

Lad os komme igang

Du ved sandsynligvis allerede, hvordan du bruger StatelessWidget & StatefulWidget. Men disse widgets komponerer kun de andre widgets. At lægge widgets ud og gengive dem sker andetsteds.

Jeg kan varmt anbefale at åbne din foretrukne IDE og følge med, se strukturer i den faktiske kode skaber ofte disse "aha" øjeblikke. I Intellij kan du dobbeltklikke på Skift og indtaste et klassens navn for at finde det.

Uklarheden

For at blive fortrolig med de grundlæggende koncepter for, hvordan fladder fungerer, vil vi se på Opacity-widgeten og undersøge det. Fordi det er en temmelig grundlæggende widget, er det et godt eksempel til at følge med.

Det accepterer kun et barn. Derfor kan du indpakke enhver widget i en Opacity og ændre den måde, den vises på. Udover barnet er der kun en parameter kaldet opacitet, som er en værdi mellem 0,0 og 1,0. Det styrer opaciteten (duh).

Widgeten

Opacity er en SingleChildRenderObjectWidget.

Hierarkiet med udvidelsesklasseudvidelser går sådan ud:

Opacitet → SingleChildRenderObjectWidget → RenderObjectWidget → Widget

I modsætning hertil går StatelessWidget og StatefulWidget som følger:

StatelessWidget / StatefulWidget → Widget

Forskellen ligger i det faktum, at Stateless / StatefulWidget kun sammensætter widgets, mens Opacity-widgeten faktisk ændrer, hvordan widget'en tegnes.

Men hvis du ser på nogen af ​​disse klasser, finder du ikke nogen kode, der er relateret til faktisk at male uklarheden.

Det skyldes, at en widget kun indeholder konfigurationsoplysninger. I dette tilfælde holder opacitetswidget kun opacitetsværdien.

Dette er grunden til, at du kan oprette en ny widget, hver gang build-funktionen kaldes. Fordi widgets ikke er dyre at konstruere. De er kun containere til information.

Rendering

Men hvor sker gengivelsen?

Det er inde i RenderObjects

Som du måske har gætt ud fra navnet, er RenderObject ansvarlig for et par ting, herunder gengivelse.

Opacity-widgeten opretter og opdaterer et RenderObject med disse metoder.

@override
RenderOpacity createRenderObject (BuildContext-kontekst) => ny RenderOpacity (opacitet: opacitet);

@override
void updateRenderObject (BuildContext-kontekst, RenderOpacity renderObject) {
  renderObject.opacity = opacitet;
}

kilde

RenderOpacity

Opacity-widgeten størrelse sig selv til at være nøjagtig den samme størrelse som dets barn. Det efterligner grundlæggende alle aspekter af barnet, men maleriet. Før man maler sit barn, tilføjer det en opacitet til det.

I dette tilfælde skal RenderOpacity implementere alle metoderne (for eksempel udføre layout / hit test / computing størrelser) og bede sit barn om at udføre det faktiske arbejde.

RenderOpacity udvider RenderProxyBox (som blandes i et par andre klasser). Disse implementerer nøjagtigt disse metoder og udsætter den faktiske beregning til det eneste barn.

dobbelt få opacitet => _opacitet;
dobbelt _kapacitet;
indstil opacitet (dobbeltværdi) {
  _kapacitet = værdi;
  markNeedsPaint ();
}

Jeg fjernede en masse påstande / optimeringer i denne kode. Se originalen for den fulde metode. kilde

Felter udsætter normalt en getter for den private variabel. Og en setter, der ud over at indstille feltet også kalder markNeedsPaint () eller markNeedsLayout (). Som navnet antyder, fortæller det systemet “Hej jeg har ændret, vær venlig at male igen / relayout”.

Inde i RenderOpacity finder vi denne metode:

@override
void paint (MaleriContext kontekst, Offset offset) {
    context.pushOpacity (offset, _alpha, super.paint);
}

Igen fjernet optimering og påstande. kilde

PaintContext er dybest set et fancy lærred. Og på dette smarte lærred er der en metode, der kaldes pushOpacity. BINGO.

Denne ene linje er den faktiske opacitetsimplementering.

At sammenfatte

  • Opaciteten er ikke en StatelessWidget eller en StatefulWidget, men i stedet for en SingleChildRenderObjectWidget.
  • Widgeten indeholder kun oplysninger, som rendereren kan bruge.
  • I dette tilfælde holder opaciteten en dobbelt, der repræsenterer opaciteten.
  • RenderOpacity, der udvider RenderProxyBox, udfører den egentlige layout / gengivelse osv.
  • Fordi opaciteten opfører sig stort set nøjagtigt som sit barn, delegerer den enhver metodekald til barnet.
  • Det tilsidesætter malingsmetoden og kalder pushOpacity, der tilføjer den ønskede opacitet til widgetten.

Det er det? Slags.

Husk, widget'en er kun en konfiguration, og RenderObject administrerer kun layout / gengivelse osv.

I Flutter genskaber du widgets stort set hele tiden. Når dine build () -metoder bliver kaldt, opretter du en masse widgets. Denne build-metode kaldes hver gang noget ændrer sig. Når der for eksempel sker en animation, kaldes build-metoden meget ofte. Det betyder, at du ikke kan genopbygge hele undertræet hver gang. I stedet vil du opdatere det.

Du kan ikke få størrelse eller placering på skærmbilledet på en widget, fordi en widget er som en plan, den er faktisk ikke på skærmen. Det er kun en beskrivelse af, hvilke variabler det underliggende render-objekt skal bruge.

Introduktion af elementet

Elementet er en konkret widget i det store træ.

Grundlæggende hvad der sker er:

Første gang, når der oprettes en widget, pumpes den op til et element. Elementet bliver derefter indsat det i træet. Hvis widgeten senere ændres, sammenlignes den med den gamle widget, og elementet opdateres i overensstemmelse hermed. Det vigtige er, at elementet ikke bliver genopbygget, det bliver kun opdateret.

Elementer er en central del af kernerammen, og der er åbenbart mere ved dem, men i øjeblikket er dette nok information.

Hvor oprettes elementet i opacitetseksemplet?

Bare et lille afsnit til dem, der er nysgerrige.

SingleChildRenderObjectWidget opretter det.

@override
SingleChildRenderObjectElement createElement () => nyt SingleChildRenderObjectElement (dette);

kilde

Og SingleChildRenderObjectElement er bare et element, der har enebarn.

Elementet opretter RenderObject, men i vores tilfælde opretter widget'en Opacity sin egen RenderObject?

Dette er bare til et glat API. Fordi oftere ikke er det, behøver widgetten et RenderObject, men intet tilpasset element. RenderObject er faktisk oprettet af elementet, lad os se på:

SingleChildRenderObjectElement (SingleChildRenderObjectWidget widget): super (widget);

kilde

SingleChildRenderObjectElement får en henvisning til RenderObjectWidget (som har metoderne til at oprette et RenderObject).

Monteringsmetoden er det sted, hvor elementet indsættes i elementtræet, og her sker magien (RenderObjectElement-klassen):

@override
void mount (Element-overordnet, dynamisk newSlot) {
  super.mount (forælder, newSlot);
  _renderObject = widget.createRenderObject (dette);
  attachRenderObject (newSlot);
  _firtig = falsk;
}

(Lige efter super.mount (forælder, newSlot); kilde

Kun én gang (når det er monteret) spørger det widgetten "Giv mig det renderobjekt, du gerne vil bruge, så jeg kan gemme det".

Slutningen

Og det er det. Sådan fungerer opacitets widget'en internt.

Mit mål med dette indlæg var at introducere dig for verden ud over widgets. Der er stadig mange emner at dække, men jeg håber, jeg kunne give dig en dejlig introduktion til den indre arbejdsgang.

Stor tak til Simon Lightfoot for at hjælpe mig med at undersøge dette!