Comment fonctionne Flutter


Comment Flutter fonctionne-t-il réellement?


Que sont les widgets, les éléments, BuildContext, RenderOject, Bindings? ..


Difficulté: débutant


Entrée


L'année dernière ( note: en 2018 ), lorsque j'ai commencé mon voyage dans le monde fabuleux de Flutter, il y avait très peu d'informations sur Internet par rapport à ce qu'elles sont aujourd'hui. Maintenant, malgré le fait que beaucoup de documents ont déjà été écrits, seule une petite partie d'entre eux parle du fonctionnement réel de Flutter.


Que sont les widgets ( widgets ), les éléments ( éléments ), BuildContext? Pourquoi Flutter est-il rapide? Pourquoi parfois cela ne fonctionne pas comme prévu? Quels sont les arbres et pourquoi sont-ils nécessaires?


Dans 95% des cas, lors de l'écriture d'une application, vous ne traiterez que des widgets afin d'afficher quelque chose ou d'interagir avec. Mais ne vous êtes-vous jamais vraiment demandé comment fonctionne toute cette magie à l'intérieur? Comment le système sait-il quand actualiser l'écran et quelles parties doivent être mises à jour?


Contenu:



Partie 1: Contexte


La première partie de l'article présente quelques concepts clés qui seront utilisés dans la deuxième partie du matériel et aideront à mieux comprendre Flutter.


Un peu sur l'appareil


Commençons par la fin et revenons aux bases.


Lorsque vous regardez votre appareil ou, plus précisément, l'application qui s'exécute sur votre appareil, vous ne voyez que l'écran.


En fait, tout ce que vous voyez, ce sont les pixels, qui forment ensemble une image bidimensionnelle, et lorsque vous touchez l'écran avec votre doigt, l'appareil ne reconnaît que la position de votre doigt sur la vitre.


Toute la magie de l'application (d'un point de vue visuel) est dans la plupart des cas de mettre à jour cette image en fonction des interactions suivantes:


  • avec l'écran de l'appareil ( par exemple, un doigt sur la vitre )
  • avec le réseau ( par exemple, communication avec le serveur )
  • au fil du temps ( par exemple animation )
  • avec d'autres capteurs externes

La visualisation de l'image sur l'écran est assurée par du matériel (affichage), qui met régulièrement (généralement 60 fois par seconde) à jour l'affichage. C'est ce qu'on appelle le "taux de rafraîchissement" et est exprimé en Hz (Hertz).


L'écran reçoit des informations à afficher du GPU (Graphics Processing Unit), qui est un circuit électronique spécialisé optimisé et conçu pour former rapidement des images à partir de certaines données (polygones et textures). Le nombre de fois par seconde que le processeur graphique peut générer une «image» (= tampon d'image) pour l'afficher et l'envoyer au matériel s'appelle la fréquence d'images ( note: fréquence d'images ). Ceci est mesuré en utilisant un bloc d'images par seconde ( par exemple 60 images par seconde ou 60 images par seconde ).


Vous pouvez me demander pourquoi j'ai commencé cet article avec les concepts d'une image bidimensionnelle affichée par un GPU / matériel et un capteur de verre physique, et quel est le lien avec les widgets Flutter réguliers?


Je pense qu'il sera plus facile de comprendre comment Flutter fonctionne réellement si nous le regardons de ce point de vue, car l'un des principaux objectifs de l'application Flutter est de créer cette image bidimensionnelle et de lui permettre d'interagir avec elle. Aussi parce que dans Flutter, croyez-le ou non, presque tout est dû à la nécessité de mettre à jour l'écran rapidement et au bon moment!


Interface entre le code et l'appareil


Quoi qu'il en soit, tous ceux qui s'intéressent à Flutter ont déjà vu l'image suivante qui décrit l' architecture de haut niveau de Flutter.



Lorsque nous écrivons une application Flutter à l'aide de Dart, nous restons au niveau Flutter Framework (surligné en vert).


Le cadre Flutter interagit avec le moteur Flutter (en bleu) via une couche d'abstraction appelée Window . Ce niveau d'abstraction fournit un certain nombre d'API pour l'interaction indirecte avec l'appareil.


Également à travers ce niveau d'abstraction, le moteur Flutter avertit le cadre Flutter lorsque:


  • un événement d'intérêt se produit au niveau de l'appareil (changement d'orientation, changement de paramètres, problème de mémoire, état de fonctionnement de l'application ...)
  • un événement se produit au niveau du verre (= geste)
  • canal de plate-forme envoie des données
  • mais aussi principalement lorsque le moteur Flutter est prêt à rendre un nouveau cadre

Gérer le rendu Flutter Engine Flutter Engine


C'est difficile à croire, mais c'est vrai. Sauf dans certains cas ( voir ci-dessous ), aucun code Flutter Framework n'est exécuté sans démarrer le rendu Flutter Engine .


Exceptions:


  • Geste / Geste (= événement sur verre)
  • Messages de la plateforme (= messages générés par un appareil, comme le GPS)
  • Messages de l'appareil (= messages liés à un changement d'état de l'appareil, par exemple, orientation, application envoyée en arrière-plan, alertes de mémoire, paramètres de l'appareil ...)
  • Réponses futures ou http

(Entre nous, vous pouvez réellement appliquer un changement visuel sans appeler du moteur Flutter, mais ce n'est pas recommandé )


Vous me demandez: "Si une sorte de code lié au geste est exécuté et provoque un changement visuel, ou si j'utilise une minuterie pour définir la fréquence de la tâche qui conduit à des changements visuels (par exemple, une animation), alors comment ça marche?"


Si vous souhaitez qu'un changement visuel se produise ou qu'un code soit exécuté sur la base d'une minuterie, vous devez indiquer au moteur Flutter que quelque chose doit être dessiné.


Habituellement, la prochaine fois que le moteur Flutter est mis à jour, il fait appel à Flutter Framework pour exécuter du code et fournit finalement une nouvelle scène pour le rendu.


Par conséquent, une question importante est de savoir comment le moteur Flutter organise tous les comportements des applications en fonction du rendu.


Pour avoir une idée des mécanismes internes, regardez l'animation suivante:



Une brève explication (plus de détails viendront plus tard):


  • Certains événements externes (geste, réponses http, etc.) ou même futurs peuvent déclencher des tâches qui nécessitent de mettre à jour l'affichage. Le message correspondant est envoyé au moteur Flutter (= Schedule Frame )
  • Lorsque le moteur Flutter est prêt à commencer la mise à jour du rendu, il crée une demande Begin Frame
  • Cette requête Begin Frame est interceptée par Flutter Framework , qui effectue des tâches principalement liées aux tickers (par exemple, l'animation)
  • Ces tâches peuvent recréer la demande pour un rendu ultérieur (exemple: l'animation n'a pas terminé son exécution, et pour la terminer, elle devra obtenir une autre image de début à un stade ultérieur)
  • Ensuite, le moteur Flutter envoie un cadre de dessin , qui est intercepté par le cadre Flutter , qui recherchera toutes les tâches liées à la mise à jour de la mise en page en termes de structure et de taille
  • Une fois toutes ces tâches terminées, il passe aux tâches associées à la mise à jour de la mise en page en termes de rendu
  • S'il y a quelque chose sur l'écran qui doit être dessiné, alors une nouvelle scène ( scène ) pour la visualisation est envoyée au moteur Flutter , qui mettra à jour l'écran
  • Flutter Framework effectue ensuite toutes les tâches qui seront effectuées après le rendu (= rappels PostFrame), et toutes les autres tâches ultérieures qui ne sont pas liées au rendu
  • ... et ce processus recommence

RenderView et RenderObject


Avant de plonger dans les détails du workflow, il est temps d'introduire le concept de l' arbre de rendu .


Comme mentionné précédemment, tout sera finalement converti en pixels qui seront affichés à l'écran, et Flutter Framework convertira les widgets que nous utilisons pour développer l'application en blocs visuels qui seront affichés à l'écran.


Ces parties visuelles correspondent à des objets appelés RenderObject , qui sont utilisés pour:


  • définir une certaine zone de l'écran en termes de taille, position, géométrie, ainsi qu'en termes de "contenu rendu"
  • identifier les zones de l'écran qui peuvent être affectées par les gestes (= toucher du doigt)

Un ensemble de tous les objets de rendu forme un arbre appelé arbre de rendu . En haut de cet arbre (= racine ), nous trouvons un RenderView .


RenderView fournit une surface commune pour les objets d' arbre de rendu et est une version spéciale de RenderObject .


Visuellement, nous pourrions représenter tout cela comme suit:


La relation entre Widget et RenderObject sera discutée plus loin. En attendant, il est temps d'aller un peu plus loin ...


Liaisons d'initialisation


Lorsque l'application Flutter démarre, la fonction main() est appelée en premier, ce qui appelle finalement la runApp(Widget app) .


Lorsque la méthode runApp() est runApp() Flutter Framework initialise les interfaces entre lui-même et le moteur Flutter . Ces interfaces sont appelées liaisons ( remarque: liaisons ).


Introduction aux liaisons


Les liaisons sont conçues pour être le lien entre le cadre et le moteur Flutter. Ce n'est que par le biais de liaisons que les données peuvent être échangées entre Flutter Framework et Flutter Engine .
(Il n'y a qu'une seule exception à cette règle - RenderView , mais nous en discuterons plus tard).


Chaque liaison est responsable du traitement d'un ensemble de tâches, actions, événements spécifiques, regroupés par domaine d'activité.


Au moment d'écrire ces lignes , le cadre Flutter a 8 liaisons.


Voici 4 d'entre eux qui seront considérés dans cet article:


  • SchedulerBinding
  • Reliure par geste
  • Reliure de rendu
  • Liaison des widgets

Pour être complet, je mentionnerai les 4 autres:


  • ServicesBinding : responsable du traitement des messages envoyés par le canal de la plateforme
  • PaintingBinding : responsable du traitement du cache d'image
  • SemanticsBinding : réservé à l'implémentation ultérieure de tout ce qui touche à la sémantique
  • TestWidgetsFlutterBinding : utilisé par la bibliothèque de tests de widgets

Vous pouvez également mentionner WidgetsFlutterBinding , mais ce n'est pas vraiment une liaison, mais plutôt une sorte d ' "initialiseur de liaison" .


Le diagramme suivant montre l'interaction entre les liaisons, que je vais examiner ensuite, et le moteur Flutter .



Examinons chacune de ces liaisons «principales».


SchedulerBinding


Cette liaison a deux responsabilités principales:


  • Dites Flutter Engine : "Hé! La prochaine fois que vous n'êtes pas occupé, réveillez-moi pour que je puisse travailler un peu et vous dire quoi rendre, ou si j'ai besoin que vous m'appeliez plus tard ..."
  • Écoutez et réagissez à ces «réveils troublants» (voir ci-dessous)

Quand SchedulerBinding demande un réveil ?


  • Quand Ticker doit élaborer une nouvelle tick


    Par exemple, vous avez une animation, vous la démarrez. L'animation est recadrée à l'aide du Ticker , qui est appelé à intervalles réguliers (= tick ) pour effectuer un rappel . Afin de lancer un tel rappel , nous devons informer le moteur Flutter afin qu'il nous réveille lors de la prochaine mise à jour (= Begin Frame ). Cela lancera le rappel du ticker pour terminer sa tâche. Si le ticker doit encore continuer son exécution, à la fin de sa tâche, il appellera SchedulerBinding pour planifier une autre trame.


  • Quand mettre à jour l'affichage


    Par exemple, nous devons élaborer un événement qui entraîne un changement visuel (exemple: mise à jour de la couleur d'une partie de l'écran, défilement, ajout / suppression de quelque chose à l'écran), pour cela, nous devons prendre les mesures nécessaires pour finalement afficher l'image mise à jour sur l'écran. Dans ce cas, lorsqu'une telle modification se produit, Flutter Framework appelle SchedulerBinding pour planifier une autre image à l'aide du moteur Flutter . (Plus tard, nous verrons comment cela fonctionne réellement)



Reliure par geste


Cette liaison écoute l'interaction avec le moteur en termes de «doigt» (= geste ).


Il est notamment chargé de recevoir les données relatives aux doigts et de déterminer avec quelle (s) partie (s) de l'écran les gestes fonctionnent. Il informe ensuite en conséquence / de ces pièces.


Reliure de rendu


Cette liaison est le lien entre le moteur Flutter et l' arbre de rendu . Elle est responsable de:


  • écouter les événements générés par le moteur pour informer des changements appliqués par l'utilisateur via les paramètres de l'appareil qui affectent les effets visuels et / ou la sémantique
  • message au moteur sur les changements qui seront appliqués à l'affichage

Pour fournir les modifications qui seront affichées à l'écran, RendererBinding est responsable de la gestion de PipelineOwner et de l'initialisation de RenderView .


PipelineOwner est une sorte d' orchestre qui sait ce qui doit être fait avec RenderObject conformément au composant et coordonne ces actions.


Liaison des widgets


Cette liaison écoute les modifications appliquées par l'utilisateur via les paramètres du périphérique qui affectent la langue (= locale ) et la sémantique .


Petite note

Je suppose qu'à un stade ultérieur du développement de Flutter, tous les événements liés à la sémantique seront transférés à SemanticsBinding , mais au moment de la rédaction de ce document, ce n'est pas le cas.

De plus, WidgetsBinding est le lien entre les widgets et le moteur Flutter . Elle est responsable de:


  • gestion du processus de traitement des modifications de la structure des widgets
  • rendre l'appel

Le traitement des modifications apportées à la structure des widgets est effectué à l'aide de BuildOwner .


BuildOwner garde la trace des widgets à reconstruire et gère les autres tâches qui s'appliquent à la structure du widget dans son ensemble.


Partie 2. Des widgets aux pixels


Maintenant que nous avons appris les bases du travail interne de Flutter , il est temps de parler des widgets.


Dans toute la documentation Flutter, vous lirez tous les widgets (widgets).


C'est presque correct. Mais pour être un peu plus précis, je dirais plutôt:


Du côté du développeur, tout ce qui concerne l'interface utilisateur en termes de mise en page et d'interaction se fait à l'aide de widgets.

Pourquoi tant de précision? En plus du fait que Widget permet au développeur de déterminer une partie de l'écran en termes de taille, de contenu, de mise en page et d'interaction, MAIS il y a beaucoup plus. Alors qu'est-ce que Widget vraiment?


Configuration immuable


Si vous regardez le code source de Flutter , vous remarquerez la définition suivante de la classe Widget .


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

Qu'est-ce que cela signifie?


L'annotation "@immutable" est très importante et nous indique que toute variable de la classe Widget doit être FINALE , en d'autres termes: "définie et affectée UNE FOIS POUR TOUS ." Ainsi, après avoir créé une instance, Widget ne pourra plus modifier ses variables internes.


Étant donné que Widget est immuable, il peut être considéré comme une configuration statique.

La structure hiérarchique des widgets


Lorsque vous concevez avec Flutter, vous définissez la structure de vos écrans à l'aide de widgets quelque chose comme ceci:


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

Cet exemple utilise 7 widgets qui forment ensemble une structure hiérarchique. Un schéma très simplifié basé sur ce code est le suivant:



Comme vous pouvez le voir, le diagramme présenté ressemble à un arbre, où SafeArea est sa racine.


Forêt derrière les arbres


Comme vous le savez déjà, un widget lui-même peut être une agrégation d'autres widgets. Par exemple, vous pouvez modifier le code précédent comme suit:


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

Cette option suppose que le widget "MyOwnWidget" lui-même affichera SafeArea , Scaffold . Mais la chose la plus importante dans cet exemple est que


Un widget peut représenter une feuille, un nœud dans un arbre, voire l'arbre lui-même ou, pourquoi pas, une forêt d'arbres ...

Comprendre l' élément dans un arbre


Qu'est-ce que cela a à voir avec ça?


Comme nous le verrons plus loin, afin de pouvoir générer les pixels qui composent l'image affichée sur l'appareil, Flutter doit connaître en détail toutes les petites parties qui composent l'écran, et afin de déterminer toutes les parties, il doit connaître l' expansion de tous les widgets.


Pour illustrer ce point, considérons le principe d'une poupée imbriquée: lorsqu'elle est fermée, vous ne voyez qu'une seule poupée, mais elle en contient une autre, qui en contient une autre et ainsi de suite ...



Lorsque Flutter étend tous les widgets (une partie de l'écran) , ce sera comme obtenir toutes les poupées (une partie de l'ensemble) .


L'image ci-dessous montre une partie de la structure hiérarchique finale des widgets correspondant au code précédent. En jaune, j'ai mis en évidence les widgets mentionnés dans le code plus tôt, afin que vous puissiez les définir dans l'arborescence finale.



Précision importante

Le langage "Widget tree" n'existe que pour faciliter la compréhension, car les programmeurs utilisent des widgets, mais il n'y a AUCUNE arborescence de widgets dans Flutter!

En fait, il serait plus correct de dire "arbre des éléments"

Il est temps d'introduire le concept d'un élément .


Chaque widget a un élément. Les éléments sont connectés les uns aux autres et forment un arbre. Par conséquent, un élément est une référence à quelque chose dans l'arbre.

Pour commencer, considérez un élément comme un nœud qui a un parent et éventuellement un enfant. En les reliant par le biais d'une relation parent-enfant , nous obtenons une structure arborescente.



Comme vous pouvez le voir, l'élément pointe vers un widget, et peut également pointer vers un objet de rendu .


Encore mieux ... Element pointe vers Widget qui a créé cet élément!

Résumons:


  • Il n'y a pas d'arborescence de widgets, mais il y a une arborescence d'éléments
  • Les éléments sont créés par des widgets.
  • L'élément fait référence au widget qui l'a créé.
  • Éléments liés aux relations avec les parents
  • Un article peut avoir un «bébé».
  • Les éléments peuvent également pointer vers un objet de rendu.

Les éléments déterminent comment les parties des blocs affichés sont liées les unes aux autres.

Afin de mieux imaginer où se situe le concept d' un élément , regardons la représentation visuelle suivante:



Comme vous pouvez le voir, l'arborescence des éléments est la relation réelle entre les widgets et les objets de rendu .


Mais pourquoi Widget crée-t-il un élément ?


3 catégories de widgets


Dans Flutter, les widgets sont divisés en 3 catégories, je les appelle personnellement comme suit (mais ce n'est que ma façon de les classer) :


  • Proxy


    L'objectif principal de ces widgets est de stocker certaines informations (qui devraient être accessibles aux widgets), faisant partie de l'arborescence basée sur Proxy. Un exemple de tels widgets est InheritedWidget ou LayoutId .


    Ces widgets ne participent pas directement à la formation de l'interface utilisateur, mais sont utilisés pour obtenir les informations qu'ils peuvent fournir.


  • Renderer


    Ces widgets sont directement liés à la disposition de l'écran, car ils déterminent (ou sont utilisés pour déterminer) la taille , la position , le rendu . Des exemples typiques sont: Row , Column , Stack , ainsi que Padding , Align , Opacity , RawImage ...


  • Composant


    Ce sont d'autres widgets qui fournissent directement non pas les informations finales liées aux tailles, positions, apparences, mais plutôt les données (ou astuces) qui seront utilisées pour obtenir les informations très finales. Ces widgets sont communément appelés composants.


    Exemples: RaisedButton , Scaffold , Text , GestureDetector , Container ...




Ce fichier PDF répertorie la plupart des widgets regroupés par catégorie.


Pourquoi cette séparation est-elle importante? Parce que selon la catégorie du widget, le type d'élément correspondant est associé à ...


Types d'articles


Il existe plusieurs types d'éléments:



Comme vous pouvez le voir dans l'image ci-dessus, les éléments sont divisés en 2 types principaux:


  • Componententlement


    Ces éléments ne sont pas directement responsables du rendu d'une partie de l'affichage.


  • RenderObjectElement


    Ces éléments sont responsables de parties de l'image affichée à l'écran.



Super! Tant d'informations, mais comment tout cela est-il lié les uns aux autres et pourquoi est-il intéressant d'en parler?


Comment les widgets et les éléments fonctionnent ensemble


Dans Flutter, toutes les mécaniques sont basées sur l'invalidation d'un élément ou d'un renderObject.

L'invalidation d'élément peut être effectuée des manières suivantes:


  • en utilisant setState , qui invalide l'intégralité de StatefulElement (notez que je ne dis pas intentionnellement StatefulWidget )
  • via des notifications traitées par proxyElement (par exemple, InheritedWidget), qui invalide tout élément qui dépend de ce proxyElement

Le résultat de l' invalidation est qu'un lien vers l' élément correspondant apparaît dans la liste des éléments sales .


L' invalidation de renderObject signifie que la structure des éléments ne change pas du tout, mais il y a un changement au niveau de renderObject , par exemple:


  • changer sa taille, sa position, sa géométrie ...
  • quelque chose doit être repeint, par exemple, lorsque vous changez simplement la couleur d'arrière-plan, le style de police ...

Le résultat d'une telle invalidation est un lien vers le renderObject correspondant dans la liste des objets de rendu (renderObjects) qui doivent être reconstruits ou repeints.


Quel que soit le type d'invalidation, SchedulerBinding est appelé (vous vous en souvenez?) Pour demander au moteur Flutter de planifier une nouvelle trame.


C'est exactement le moment où le moteur Flutter "réveille" le SchedulerBinding et toute la magie opère ...


onDrawFrame ()


Plus tôt dans cet article, nous avons noté que SchedulerBinding a deux responsabilités principales, dont l'une est sa disponibilité à gérer les demandes faites par Flutter Engine liées à la reconstruction de trames. C'est le moment idéal pour se concentrer sur cela.


Le diagramme de séquence partielle ci-dessous montre ce qui se passe lorsque SchedulerBinding reçoit une demande onDrawFrame () du moteur Flutter .



Étape 1. Éléments


WidgetsBinding est appelé et cette liaison prend d'abord en compte les modifications associées aux éléments. WidgetsBinding appelle la méthode buildScope de l'objet buildOwner , car BuildOwner est responsable du traitement de l'arborescence d'éléments. Cette méthode parcourt la liste des éléments sales et demande leur reconstruction .


Les principaux principes de cette méthode de rebuild() ) sont:


  1. Il y a une demande pour reconstruire l'élément (cela prendra la plupart du temps), appelant la méthode build() du widget auquel cet élément fait référence (= Widget build (BuildContext context) {...} méthode). Cette méthode build() retournera un nouveau widget
  2. Si l'élément n'a pas «d'enfant», alors un élément est créé pour le nouveau widget (voir ci-dessous) ( note: inflateWidget ), sinon
  3. le nouveau widget est comparé à celui référencé par l'enfant de l'élément
    • S'ils sont interchangeables (= le même type de widget et la même clé ), la mise à jour se produit et l'enfant est enregistré.
    • S'ils ne sont pas interchangeables, l'enfant est supprimé ( ~ supprimé ) et un élément est créé pour le nouveau widget
  4. Ce nouvel élément est monté en tant qu'enfant de l'élément. ( monté) = inséré dans l'arborescence des éléments)

L'animation suivante va essayer de rendre cette explication un peu plus claire.



Remarque sur les widgets et les éléments


Pour un nouveau widget, un élément d'un type spécifique est créé qui correspond à la catégorie du widget, à savoir:


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

Chacun de ces types d'éléments a son propre comportement. Par exemple:


  • StatefulElement appellera la méthode widget.createState() à l'initialisation, qui créera un état et l'associera à l'élément
  • Lorsqu'un élément de type RenderObjectElement est monté, il crée un RenderObject . Ce renderObject sera ajouté à l' arbre de rendu et associé à l'élément.

Étape 2. renderObjects


Maintenant, après avoir terminé toutes les actions associées aux éléments sales , l' arbre des éléments est stable. Il est donc temps de considérer le processus de visualisation.


Étant donné que RendererBinding est responsable du rendu de l' arbre de rendu , WidgetsBinding appelle la méthode drawFrame RendererBinding .


Le diagramme partiel ci-dessous montre la séquence d'actions effectuées lors de la demande drawFrame () .



À cette étape, les actions suivantes sont effectuées:


  • Chaque renderObject marqué comme sale est demandé de le composer (c'est-à-dire de calculer sa taille et sa géométrie)
  • Chaque renderObject marqué comme "ayant besoin d'être redessiné" est redessiné en utilisant sa propre méthode de calque
  • La scène résultante est formée et envoyée au moteur Flutter , afin que ce dernier la transfère à l'écran de l'appareil
  • Enfin, la sémantique est également mise à jour et envoyée au moteur Flutter

À la fin de ce flux de travail, l'écran de l'appareil est actualisé.


Partie 3: Gérer les gestes


Les gestes (= événements liés aux actions des doigts sur la vitre ) sont traités à l'aide de GestureBinding .


Lorsque le moteur Flutter envoie des informations sur un événement de mouvement via l'API window.onPointerDataPacket , GestureBinding l' intercepte, effectue une mise en mémoire tampon et:


  1. convertit les coordonnées fournies par le moteur Flutter pour correspondre au rapport de pixels de l' appareil , puis
  2. récupère de renderView une liste de tous les RenderObjects qui se trouvent dans la partie de l'écran liée aux coordonnées de l'événement
  3. puis parcourt la liste résultante de renderObjects et envoie un événement associé à chacun d'eux
  4. si renderObject "écoute" les événements de ce type, il le traite

J'espère maintenant que je comprends l'importance des renderObjects .


Partie 4: Animations


Cette partie de l'article traite du concept d' animation et d'une compréhension approfondie de Ticker .


Lorsque vous travaillez avec des animations, vous utilisez généralement un AnimationController ou n'importe quel widget pour les animations ( remarque: AnimatedCrossFade ).


Dans Flutter, tout ce qui concerne les animations fait référence à Ticker . Ticker , lorsqu'il est actif, n'a qu'une seule tâche: "il demande à SchedulerBinding d' enregistrer un rappel et de dire au moteur Flutter de le réveiller lorsqu'un nouveau rappel apparaît." Lorsque le moteur Flutter est prêt, il appelle SchedulerBinding via une requête: " onBeginFrame ". SchedulerBinding accède à la liste de rappel du ticker et exécute chacune.


Chaque tick est intercepté par un contrôleur "intéressé" pour le traiter. Si l'animation est terminée, le ticker est «désactivé», sinon le ticker demande un SchedulerBinding pour planifier un nouveau rappel. Et ainsi de suite ...


Image complète


Nous avons maintenant appris comment fonctionne Flutter :



Buildcontext


Enfin, revenons au diagramme qui montre les différents types d'éléments, et considérons la signature de l' élément racine:


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

Nous voyons le très célèbre BuildContext ! Mais c'est quoi?


BuildContext est une interface qui définit un certain nombre de getters et de méthodes qui peuvent être implémentées par un élément. La plupart du temps, BuildContext est utilisé dans la méthode build() de StatelessWidget ou State pour StatefulWidget .


BuildContext n'est rien d'autre que l' élément lui-même, qui correspond à
  • widget en cours de mise à jour (à l'intérieur des méthodes de build ou de build )
  • StatefulWidget associé à State dans lequel vous référencez la variable de contexte.

Cela signifie que la plupart des développeurs travaillent constamment avec des éléments sans même le savoir.


Quelle est l'utilité d'un 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() .


Conclusion


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


. .


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

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


All Articles