flutter buildcontext что такое

What does BuildContext do in Flutter?

What does BuildContext do, and what information do we get out of it?

https://flutter.io/widgets-intro/#basic-widgets on the 9th instance of the term BuildContext there is an example, but it’s not clear how it is being used. It’s part of a much larger set of code that loses me, and so I am having a hard time understanding just what BuildContext is.

Can someone explain this in simple/very basic terms?

2 Answers 2

BuildContext is, like it’s name is implying, the context in which a specific widget is built.

If you’ve ever done some React before, that context is kind of similar to React’s context (but much smoother to use) ; with a few bonuses.

Generally speaking, there are 2 use cases for context :

The second point is kinda rare. On the other hand, the first point is used nearly everywhere.

Notice the context here. It’ll be used to get the closest instance of NavigatorState widget above in the tree. Then call the method pushNamed on that instance.

BuildContext is really useful when you want to pass data downward without having to manually assign it to every widgets’ configurations for example ; you’ll want to access them everywhere. But you don’t want to pass it on every single constructor.

You could potentially make a global or a singleton ; but then when confs change your widgets won’t automatically rebuild.

And then, use it this way :

And even cooler is that all widgets who call inheritFromWidgetOfExactType(Configuration) will automatically rebuild when the configurations change.

Источник

Перевод статьи Flutter in Context автора Greg Perry

Детальный разбор класса BuidContext
(Эта статья является частью серии Decode Flutter Series)

Вы уже знакомы с контекстными объектами? Я имею в виду объекты класса BuildContext с именем context, который постоянно передается функции build(), а также является необходимым параметром для множества статических функций:

Они помогают идти вверх или сквозь «дерево рендеринга» (или же «дерево виджетов»). Мы подробно рассмотрим эти объекты в этой статье, заглянем «под капот» фреймворка Flutter и выясним, из чего именно состоит этот объект BuiltContext под названием context. А это означает, что мы «пойдем» сквозь код. Собственно, не буду долго тянуть и держать вас в неведении и прямо сейчас рассажу вам, что именно это за объект: это элемент.

Нажмите на скриншот, чтобы открыть отрывок кода.

Я всегда предпочитаю использовать скриншоты в своих статьях для отображения самого концепта, а не просто кода. К тому же, я считаю, что с ними проще работать. Однако, вы можете кликнуть на изображение, чтобы открыть сам код в gist или в Github. Также эту статью лучше читать не на мобильном устройстве, а через компьютер.

Not In Context

Так уж получилось, что мы не собираемся подробно рассматривать сам класс BuildContext. Хоть он и является ключевым во фреймворке Flutter, просто представления о том, что он делает в приложении, будет вполне достаточно. К тому же, это абстрактный класс – вы должны создать подкласс и реализовать все поля и функции, что он содержит.
Ниже предоставлен скриншот класс со всей его документацией и удаленными устаревшими функциями – просто чтобы дать вам представление об его роли во Flutter. Вы даже можете узнать некоторые функции и даже удивиться тому, в каком классе они находятся. Затем мы определим точный подкласс, который использует BuildContext. Правда, эту загадку я уже раскрыл выше.

Элементы виджетов

Отойдем немного назад и посмотрим сперва на класс StatelessWidget. Ниже расположен скриншот одного такого класса со всей его документацией, чтобы вы могли рассмотреть, из чего он состоит. Не очень-то много всего, правда? Это абстрактный класс, и его подкласс, конечно, должен реализовывать метод build() – это мы уже знаем. Однако, что насчет метода createElement()? Он создает другой класс под названием StatelessElement и,фактически, ссылается на себя в качестве параметра.

Я решил оставить всю немногочисленную документацию класса StatelessElement. Обратите внимание, указанный в конструкторе параметр widget класса StatelessWidget передается родительскому классу ComponentElement, туда мы и перейдем:

Класс ComponentElement становится более обширным, так что я сделал скриншот начала этого класса. Это тоже абстрактный класс, что имеет смысл, поскольку он содержит тот самый необходимый для выполнения метод build(). Сейчас мы возвращаемся назад через иерархию классов, однако, мы еще посетим эти «промежуточные» классы через некоторое время. Сейчас мы должны обратить внимание на класс Element.

Опять-таки, мы смотрим только на начало этого класса. Он гораздо больше тех, что мы рассматривали ранее: в целом, он состоит из 90 методов и полей. В самом деле, довольно важный «элемент». Однако, мы пришли к цели. Что вы видите?

Класс Element реализует другой класс, BuildContext. В отличие от, к примеру, Java, любой класс в языке программирования Dart может быть использован, как интерфейс – вы просто добавляете его название в конце декларации класса сразу после ключевого слова implements.

Конечно, если только ваш класс не является абстрактным, вы должны реализовать все методы и поля, составляющие этот класс. В случае с классом Elements, мы реализуем класс BuildContext со всей его составляющей – не удивительно, что теперь мы видим огромное количество полей и методов. После выполнения этой непростой задачки, мы наконец-то можем заключить, что, как и во всех ориентированных на объект языках, объект типа Element может быть передан функциям или конструкторам как объект типа BuildContext.

Документация Flutter поясняет, что BuildContext было решено использовать в качестве интерфейса с целью препятствия прямому манипулированию объектами Element – что бы это ни значило. К тому же, кто вообще захочет разбираться с 90 методами и полями класса?

Так что, теперь вы знаете, что при размещении контрольной точки в своей любимой IDE прямо в строке метода build(), параметр контекста (context), переданный этму методу (в случае StatelessWidget) – тот самый объект StatelessElement, создание которого мы видели в скриншоте StatelessWidget. Обратите внимание, что это касается и StatefulWidget – только с объектом StatefulElement. Оба эти типа виджетов представлены ниже:

Помните, что у каждого виджета есть свой объект Element (или же объект BuildContext). Element/BuildContext может принадлежать только одному виджету. Когда вы только начинали изучать Flutter, скорее всего, вы пришли к выводу, что все виджеты, составляющие ваше приложение, расположены в древообразном порядке, и это правда, только не совсем. Это их сообщающиеся объекты Element, связанные друг с другом. Каждый элемент (контекст) содержит отсылку на свой «родительский» элемент, который создает тот самый элемент (контекст) при запуске приложения Flutter. Можно сказать, что приложение Flutter состоит из связанных друг с другом объектов BuildContext, которые и составляют «древо объектов BuildContext». Они остаются неизменными в течение всего цикла приложения, в то время как их аналоги-«виджеты» могут быть удалены и воссозданы вновь и вновь.

Поместим это в контекст

Я воспользуюсь приложением из другой статьи для демонстрации «процесса наполнения», задействованного при запуске приложения и того, как каждый виджет включает в себя свой собственный аналог элемента (контекста). Домашний экран приложения со всем его кодом представлен ниже. На первый взгляд, мы имеем десять виджетов:

Скриншоты ниже показывают, что происходит при вызове метода createElement для StatelessWidget. Как только это происходит, в «родительском» методе элемента, inflateWidget(), создается соответствующий объект элемента виджета. На среднем скриншоте отображена эта функция и показано, как недавно созданный объект элемента вызывает свою собственный метод mount(). Это происходит там же, где и «родительский» элемент привязывается к полю дочернего экземпляра _parent, добавляясь к древу рендеринга. Каждому виджету соответствует свой элемент/контекст объекта.

А теперь с Context

При помощи ссылки на объекты BuildContext у вас появляется все необходимое для возврата к дереву, чтобы получить все «выше» созданные экземпляры Widgets и объекты State. К примеру, все эти функции «of» перечислены в начале этой статьи, и каждая из них содержит функцию из BuidContext, реализованную и определенную в классе Element:

Тема оформления

Напоследок, разберем еще парочку более популярных исполнений. Работая с Flutter, вы обнаружите, что статичная функция Theme.of() используется несколько раз при работе фреймворка. Ниже слева есть скриншот, показывающий весьма распространенное действие – получение объекта ThemeData для применения определенного стиля для виджета Text. На скриншоте справа отображен определенный в классе BuidContext метод dependInheritWidgetOfExactType, используемый для поиска определенного «типа InheritedWidget» в объекте Map под названием _inheritedWidgets.

Ищем Scaffold

В приложениях Flutter также весьма распространен метод Scaffold.of(). Он содержит прочие методы, определенные в классе BuildContext и реализованные в классе Element. Также он идет вверх через весь список связанных объектов Element, чтобы найти ранее определенный объект State. Практически целый цикл связан с возвращением через связанный список объектов Element ( каждый из них связан со своим «родителем») в целях поисках виджета определенного типа. В нашем случае выполняется поиск Scaffold, определенного ранее в древе рендеринга, а затем возвращается его связанный объект State под названием ScaffoldState.

Читайте также:  какой камень подходит тельцам женщинам рожденным в год собаки

Пока что остановимся на этом. Класс Element реализует класс BuildContext, и, возможно, чуть позже мы обратимся к ряду других функций, с помощью которых можно извлекать множество «элементов» вашего приложения, если они уже были созданы и упомянуты как часть древа рендеринга.

Источник

BuildContext class Null safety

A handle to the location of a widget in the widget tree.

This class presents a set of methods that can be used from StatelessWidget.build methods and from methods on State objects.

BuildContext objects are passed to WidgetBuilder functions (such as StatelessWidget.build), and are available from the State.context member. Some static functions (e.g. showDialog, Theme.of, and so forth) also take build contexts so that they can act on behalf of the calling widget, or obtain data specifically for the given context.

Each widget has its own BuildContext, which becomes the parent of the widget returned by the StatelessWidget.build or State.build function. (And similarly, the parent of any children for RenderObjectWidgets.)

In particular, this means that within a build method, the build context of the widget of the build method is not the same as the build context of the widgets returned by that build method. This can lead to some tricky cases. For example, Theme.of(context) looks for the nearest enclosing Theme of the given build context. If a build method for a widget Q includes a Theme within its returned widget tree, and attempts to use Theme.of passing its own context, the build method for Q will not find that Theme object. It will instead find whatever Theme was an ancestor to the widget Q. If the build context for a subpart of the returned tree is needed, a Builder widget can be used: the build context passed to the Builder.builder callback will be that of the Builder itself.

For example, in the following snippet, the ScaffoldState.showBottomSheet method is called on the Scaffold widget that the build method itself creates. If a Builder had not been used, and instead the context argument of the build method itself had been used, no Scaffold would have been found, and the Scaffold.of function would have returned null.

The BuildContext for a particular widget can change location over time as the widget is moved around the tree. Because of this, values returned from the methods on this class should not be cached beyond the execution of a single synchronous function.

BuildContext objects are actually Element objects. The BuildContext interface is used to discourage direct manipulation of Element objects.

Источник

Русские Блоги

Flutter | Глубокое понимание BuildContext

Предисловие

Недавно некоторые студенты, которые были новичками во Flutter, столкнулись с этой проблемой, когда выполняли переходы по страницам.

На первый взгляд кажется, что проблем нет, и решение очень простое, просто удалите домашнюю часть как новый виджет.

Но когда вы впервые столкнулись с этими вещами, это должно быть очень устрашающе. Что, черт возьми, такое BuildContext, зачем нам нужно передавать BuildContext каждый раз, когда мы создаем функцию? Почему моя операция навигатора показывает, что текущий контекст не может найти навигатор? Почему он просто разделен на новый виджет?

Итак, сегодня я хочу ответить на этот вопрос, чтобы поделиться с вами тем, как понимать и использовать BuildContext во Flutter. Также будут задействованы некоторые части процесса создания виджета.Перед официальным запуском кратко объясните эти концепции.

Что такое Navigator и что делает MaterialApp

Мы часто открываем много страниц в приложении. Когда мы вернемся, оно вернется к последней открытой странице, а затем вернется назад слой за слоем. Да, это стек. Во Flutter за управление и поддержание этих стопок страниц отвечает навигатор.

Обычно мы не создаем навигатор вручную при создании приложения, но мы также можем выполнять навигацию по страницам. Вот почему.

Узнав роль Navigator и MaterialApp, давайте взглянем на BuildContext.

BuildContext

Каждый раз, когда мы пишем часть кода интерфейса, мы работаем в функции сборки. Функция сборки по умолчанию должна передавать BuildContext. Посмотрим, что это такое.

Мы видим, что BuildContext на самом деле является абстрактным классом, но то, что передается в каждой функции сборки. Давайте посмотрим, что происходит, когда мы строим представление.

Как Flutter создает просмотры

Давайте посмотрим, что происходит при построении представления. В качестве примера возьмем виджет без сохранения состояния.

Если вы хотите установить этот виджет в дерево представления, сначала перейдите к createElement и передайте текущий виджет в Element.

Давайте посмотрим, что это за StatelessElement

Мы видим, что, передавая виджет конструктору StatelessElement, StatelessElement сохраняет ссылку на виджет и вызывает метод сборки.

Метод сборки фактически вызывает метод сборки виджета и передает его, который является объектом StatelessElement. Мы знаем, что метод сборки должен передаваться в BuildContext, почему он передается в StatelessElement? Так что продолжаем смотреть.

Итак, давайте теперь посмотрим на официальное объяснение BuildContext:

BuildContextobjects are actually Element objects. The BuildContextinterface is used to discourage direct manipulation of Element objects.

Объект BuildContext на самом деле является объектом Element, а интерфейс BuildContext используется для предотвращения прямых операций с объектом Element.

Круто! Теперь мы наконец знаем, откуда появился этот BuildContext. Давайте разберемся, что делает флаттер для построения обзора.

Просмотр процесса загрузки дерева

StatelessWidget

StatefulWidget

of (context) метод

Мы часто используем такой код во флаттере

Так что же это (контекст)? Здесь мы берем Navigator, чтобы открыть новую страницу в качестве примера.

Как вы можете видеть, часть кода клавиши проходит дерево элементов вверх через context.rootAncestorStateOfType и находит ближайший соответствующий NavigatorState. Другими словами, of на самом деле является инкапсуляцией контекста для получения данных по компонентам.

Операция push нашего навигатора выполняется через найденное NavigatorState.

Мало того, BuildContext также имеет множество методов для получения объектов из компонентов.

Следует отметить, что на этапе initState состояния State данные не могут быть получены между компонентами.Эти методы можно использовать только после didChangeDependencies.

Просмотрите проблему

Давайте теперь посмотрим на проблему отсутствия навигатора в текущем контексте, с которой мы столкнулись раньше, это очень просто.

Итак, когда мы дизассемблируем часть Scaffold в другой виджет, мы получаем BuildContext для FirstPage в функции сборки FirstPage, затем ищем и находим MaterialApp и находим предоставляемый им навигатор, чтобы мы могли с радостью перейти на страницу. Вверх.

Справка

Напишите в конце

У моего пациента с прокрастинацией на поздней стадии был период времени, прежде чем мои личные дела были очень заняты, и я был прерван. Я не ожидал, что одной воды хватит на 3 месяца. Видя, как большие парни так много работают, я думаю, что это слишком неприятно, чтобы думать об этом. Наконец продолжайте вести блог снова. 🤣

Если что-то не так со статьей, укажите это. Добро пожаловать, чтобы оставить сообщение в поле для комментариев ниже и на мой адрес электронной почты [email protected], я свяжусь с вами в течение 24 часов!

Я не представлял, какая будет следующая статья, возможно, это будет практическая серия. 😌

Источник

Как работает Flutter

Как Flutter работает на самом деле?

Что такое Widgets, Elements, BuildContext, RenderOject, Bindings.

Вступление

В прошлом году (прим: в 2018), когда я начал свое путешествие в сказочный мир Flutter, в Интернете было очень мало информации по сравнению с тем, что есть сегодня. Сейчас, несмотря на то, что уже написано много материалов, лишь небольшая их часть рассказывает о том, как на самом деле работает Flutter.

Что же такое Widgets (виджеты), Elements (элементы), BuildContext? Почему Flutter быстрый? Почему иногда он работает не так, как ожидается? Что такое деревья и зачем они нужны?

В 95% случаев при написании приложения вы будете иметь дело только с виджетами, чтобы что-то отображать на экране или взаимодействовать с ним. Но неужели вы никогда не задумывались, как вся эта магия работает внутри? Как система узнает, когда обновить экран и какие части должны быть обновлены?

Содержание:

Часть 1: Предыстория

В первой части статьи представлены некоторые ключевые понятия, которые будут использованы во второй части материала и помогут лучше понять Flutter.

Читайте также:  что делает медсестра в гинекологии

Немного об устройстве

Давайте начнем с конца и вернемся к основам.

Когда вы смотрите на свое устройство или, точнее, на приложение, запущенное на вашем устройстве, вы видите только экран.

На самом деле, всё, что вы видите – это пиксели, которые вместе составляют 2-мерное изображение, и когда вы касаетесь экрана пальцем, устройство распознает только положение вашего пальца на стекле.

Вся магия приложения (с визуальной точки зрения) в большинстве случаев заключается в обновлении этого изображения на основе следующих взаимодействий:

Визуализация изображения на экране обеспечивается аппаратным обеспечением (дисплеем), которое регулярно (обычно 60 раз в секунду) обновляет дисплей. Эта называется «частотой обновления» и выражается в Гц (Герцах).

Дисплей получает информацию для отображения от GPU (Graphics Processing Unit), представляющего собой специализированную электронную схему, оптимизированную и предназначенную для быстрого формирования изображения из некоторых данных (полигонов и текстур). Количество раз в секунду, которое графический процессор может генерировать «изображение» (=буфер кадров) для отображения и отправки его на аппаратное обеспечение, называется кадровой частотой (прим: frame rate). Это измеряется с помощью блока кадров в секунду (например, 60 кадров в секунду или 60fps).

Вы, возможно, спросите меня, почему я начал эту статью с понятий 2-мерного изображения, отображаемого GPU / аппаратным обеспечением и датчиком физического стекла и какова связь с обычными виджетами Flutter?

Думаю, что будет легче понять, как на самом деле работает Flutter, если мы посмотрим на него с этой точки зрения, так как одна из главных целей приложения Flutter – создать это 2-мерное изображение и дать возможность взаимодействовать с ним. Также потому, что во Flutter, хотите верьте, хотите нет, почти все обусловлено необходимостью обновления экрана быстро и в нужный момент!

Интерфейс между кодом и устройством

Так или иначе, все интересующиеся Flutter уже видели следующую картинку, которая описывает архитектуру высокого уровня Flutter.

Когда мы пишем приложение Flutter, используя Dart, мы остаемся на уровне Flutter Framework (выделено зеленым цветом).

Flutter Framework взаимодействует с Flutter Engine (синим цветом) через слой абстракции, называемый Window. Этот уровень абстракции предоставляет ряд API для косвенного взаимодействия с устройством.

Также через этот уровень абстракции Flutter Engine уведомляет Flutter Framework, когда:

Управление Flutter Framework рендерингом Flutter Engine

В это сложно поверить, но это правда. За исключением некоторых случаев (cм. ниже) ни один код Flutter Framework не выполняется без запуска рендеринга Flutter Engine.

(Между нами говоря, на самом деле можно применить визуальное изменение без вызова от Flutter Engine, но это не рекомендуется делать)

Вы меня спросите: «Если какой-то код, связанный с жестом, выполняется и вызывает визуальное изменение или если я использую timer для задания периодичности задачи, которая приводит к визуальным изменениям (например, анимация), то как это работает?»

Если вы хотите, чтобы произошло визуальное изменение или чтобы какой-то код выполнялся на основе таймера, то вам нужно сообщить Flutter Engine, что что-то должно быть отрисовано.

Обычно при следующем обновлении Flutter Engine обращается к Flutter Framework для выполнения некоторого кода и в конечном итоге предоставляет новую сцену для рендеринга.

Поэтому важный вопрос заключается в том, как движок Flutter организует все поведение приложения на основе рендеринга.

Чтобы вам получить представление о внутренних механизмах, посмотрите на следующую анимацию:

Краткое объяснение (более подробная информация будет позже):

RenderView и RenderObject

Прежде чем погружаться в детали, связанные с потоком действий, самое время ввести понятие Rendering Tree.

Как уже говорилось ранее, всё в конечном итоге преобразуется в пиксели, которые будут отображаться на экране, и Flutter Framework преобразует Widgets, которые мы используем для разработки приложения, в визуальные блоки, которые будут отображаться на экране.

Данные визуальные части соответствуют объектам, называемым RenderObject, которые используются для:

Набор всех RenderObject формирует дерево, называемое Render Tree. В верхней части этого дерева (= root) мы находим RenderView.

RenderView представляет общую поверхность для объектов Render Tree и является специальной версией RenderObject.

Визуально мы могли бы представить все это следующим образом:

Cвязь между Widget и RenderObject будет рассмотрена далее. А пока пришло время немного углубиться…

Инициализация bindings

Во время вызова метода runApp() Flutter Framework инициализирует интерфейсы между собой и Flutter Engine. Эти интерфейсы называются bindings (прим: привязки).

Введение в привязки

Привязки предназначены для того, чтобы быть связующим звеном между фреймворком и движком Flutter. Только с помощью привязок можно обмениваться данными между Flutter Framework и Flutter Engine.
(Есть только одно исключение из этого правила – RenderView, но мы обсудим это позже).

Каждая привязка отвечает за обработку набора конкретных задач, действий, событий, сгруппированных по области деятельности.

На момент написания этой статьи во Flutter Framework насчитывается 8 привязок.

Ниже приведены 4 из них, которые будут рассмотрены в этой статье:

Для полноты картины упомяну и остальные 4:

Можно также упомянуть WidgetsFlutterBinding, но на самом деле это не является привязкой, а скорее своего рода «инициализатором привязки».

На следующей диаграмме показано взаимодействие между привязками, которые я собираюсь рассмотреть далее, и Flutter Engine.

Давайте посмотрим на каждую из этих «основных» привязок.

SchedulerBinding

У этой привязки есть две основные обязанности:

Когда SchedulerBinding запрашивает «тревожное пробуждение»?

Когда Ticker должен отработать новый tick

Например, у вас есть анимация, вы ее запускаете. Анимация кадрируется с помощью Ticker, который с регулярным интервалом (= tick) вызывается для выполнения обратного вызова. Чтобы запустить такой обратный вызов, нам нужно сказать Flutter Engine, чтобы он «разбудил» нас при следующем обновлении (= Begin Frame). Это запустит обратный вызов ticker для выполнения его задачи. Если ticker все еще нужно продолжить выполнение, то в конце своей задачи он вызовет SchedulerBinding для планирования другого кадра.

Когда надо обновить отображение

Например, надо отработать событие, которое приводит к визуальному изменению (пример: обновление цвета части экрана, прокрутка, добавление / удаление чего-либо с экрана), для этого нам нужно предпринять необходимые шаги, чтобы в конечном итоге показать на экране обновленное изображение. В этом случае, когда происходит такое изменение, Flutter Framework вызывает SchedulerBinding для планирования другого кадра с помощью Flutter Engine. (Позже мы увидим, как это работает на самом деле)

GestureBinding

Данная привязка слушает взаимодействие с движком в терминах «пальца» (= жест).

В частности, он отвечает за прием данных, относящихся к пальцу, и за определение того, с какой частью (частями) экрана работают жесты. Затем он соответственно уведомляет об этом / этих частях.

RendererBinding

Эта привязка является связующим звеном между Flutter Engine и Render Tree. Она отвечает за:

Чтобы предоставить изменения, которые будут отображаться на экране, RendererBinding отвечает за управление PipelineOwner и инициализацию RenderView.

PipelineOwner — это своего рода оркестратор, который знает, что нужно сделать с RenderObject в соответствии с компоновой, и координирует эти действия.

WidgetsBinding

Данная привязка прослушивает изменения, применяемые пользователем через настройки устройства, которые влияют на язык (= locale) и семантику.

Я предполагаю, что на более позднем этапе развития Flutter все события, связанные с семантикой, будут перенесены в SemanticsBinding, но на момент написания этой статьи это еще не так.

Кроме этого, WidgetsBinding является связующим звеном между виджетами и Flutter Engine. Она отвечает за:

Обработка изменений структуры виджетов осуществляется с помощью BuildOwner.

BuildOwner отслеживает, какие виджеты нуждаются в перестройке, и обрабатывает другие задачи, которые применяются к структуре виджетов в целом.

Часть 2. От виджетов к пикселям

Теперь, когда мы познакомились с основами внутренней работы Flutter, пришло время поговорить о виджетах.

Во всей документации Flutter вы прочитаете, что всё Widgets (виджеты).

Это почти правильно. Но для того, чтобы быть немного более точным, я бы скорее сказал:

Со стороны разработчика, всё, что связано с пользовательским интерфейсом с точки зрения компоновки и взаимодействия, делается с помощью виджетов.

К чему такая точность? К тому, что Widget позволяет разработчику определить часть экрана с точки зрения размеров, содержания, компоновки и взаимодействия, НО за этим есть гораздо большее. Так что же такое Widget на самом деле?

Неизменяемая конфигурация

Если вы посмотрите исходный код Flutter, то заметите следующее определение класса Widget.

Аннотация «@immutable» очень важна и говорит нам, что любая переменная в классе Widget должна быть FINAL, другими словами: «определена и назначена ОДИН РАЗ ДЛЯ ВСЕХ«. Таким образом, после создания экземпляр Widget больше не сможет изменить свои внутренние переменные.

Так как Widget неизменяемый, то можно его считать статичной конфигурацией

Иерархическая структура виджетов

Когда вы разрабатываете с помощью Flutter, вы определяете структуру своего экрана(ов), используя виджеты примерно так:

Читайте также:  какой нужен документ подтверждающий что болел коронавирусом

В этом примере используется 7 виджетов, которые вместе образуют иерархическую структуру. Очень упрощенная схема, основанная на данном коде, выглядит следующим образом:

Как можно заметить, представленная схема выглядит, как дерево, где SafeArea является его корнем.

Лес за деревьями

Как вы уже знаете, виджет сам по себе может быть агрегацией других виджетов. В качестве примера можно изменить предыдущий код следующим образом:

Данный вариант предполагает, что виджет «MyOwnWidget» сам будет отображать SafeArea, Scaffold. Но самое главное в этом примере заключается в том, что

Widget может представлять лист, узел в дереве, даже само дерево или, почему бы и нет, лес деревьев.

Понимание Element в дереве

Как будет показано далее, чтобы иметь возможность генерировать пиксели, которые составляют изображение, отображаемое на устройстве, Flutter должен знать в деталях все маленькие части, которые составляют экран, и, чтобы определить все части, ему необходимо знать раскрытие всех виджетов.

Чтобы проиллюстрировать данный момент, рассмотрим принцип матрёшки: в закрытом состоянии вы видите только 1 куклу, но она содержит другую, которая в свою очередь содержит ещё одну и так далее.

Когда Flutter «раскроет» все виджеты (часть экрана), это будет похоже на получение всех кукол (часть целого).

На картинке ниже показана часть конечной иерархической структуры виджетов, соответствующая предыдущему коду. Желтым цветом я выделил виджеты, которые были упомянуты в коде ранее, чтобы вы могли определить их в финальном дереве.

Формулировка «дерево виджетов» (Widget tree) существует только для облегчения понимания, поскольку программисты используют виджеты, но во Flutter НЕТ дерева виджетов!

На самом деле, правильнее будет сказать «дерево элементов» (tree of Elements)

Настало время ввести понятие элемента (Element).

Каждому виджету соответствует один элемент. Элементы связаны друг с другом и образуют дерево. Следовательно элемент является ссылкой на что-то в дереве.

Для начала подумайте об элементе как о узле, который имеет родителя и, возможно, ребенка. Связывая их вместе через отношение родитель — ребёнок, мы получаем древовидную структуру.

Как вы можете видеть, элемент указывает на один виджет, а также может указывать на RenderObject.

Даже лучше… Element указывает на Widget, который создал этот Element!

Давайте подведём итоги:

Элементы определяют, как части отображаемых блоков связаны друг с другом

Для того, чтобы лучше представить, где понятие элемент подходит, давайте рассмотрим следующее визуальное представление:

Как вы можете заметить, дерево элементов является фактической связью между виджетами и RenderObjects.

Но почему Widget создает Element?

3 категории виджетов

Во Flutter виджеты разделены на 3 категории, лично я называю их следующим образом (но это только мой способ классифицировать их):

Основная задача этих виджетов состоит в том, чтобы хранить некоторую информацию (которая должна быть доступной для виджетов), части древовидной структуры, основанной на Proxy. Примером таких виджетов является InheritedWidget или LayoutId.

Эти виджеты не принимают непосредственного участия в формировании пользовательского интерфейса, но используются для получения информации, которую они могут предоставить.

Данные виджеты имеют непосредственное отношение к компоновке экрана, поскольку они определяют (или используются для определения) размеры, положение, отрисовку. Типичными примерами являются: Row, Column, Stack, а также Padding, Align, Opacity, RawImage.

Это другие виджеты, которые предоставляют непосредственно не окончательную информацию, связанную с размерами, позициями, внешним видом, а скорее данные (или подсказки), которые будут использоваться для получения той самой финальной информации. Эти виджеты обычно называются компонентами.

Примеры: RaisedButton, Scaffold, Text, GestureDetector, Container.

В этом PDF-файле перечислена большая часть виджетов, сгруппированных по категориям.

Почему это разделение важно? Потому что в зависимости от категории виджета, соответствующий тип элемента связан с…

Типы элементов

Есть несколько типов элементов:

Как вы можете видеть на картинке выше, элементы делятся на 2 основных типа:

Эти элементы напрямую не отвечают за отрисовку какой-либо части отображения.

Данные элементы отвечают за части отображаемого изображения на экране.

Отлично! Столько информации, но как всё это связано друг с другом и почему об этом интересно рассказать?

Как виджеты и элементы работают вместе

Во Flutter вся механика основана на инвалидации элемента или renderObject.

Инвалидация элемента может быть сделана следующими способами:

Результатом инвалидации является то, что на соответствующий элемент появляется ссылка в списке dirty элементов.

Инвалидация renderObject означает, что структура элементов никак не меняется, но происходит изменение на уровне renderObject, например:

Результатом такой инвалидации является ссылка на соответствующий renderObject в списке объектов рендеринга (renderObjects), которые необходимо перестроить или перекрасить.

Независимо от типа инвалидации вызывается SchedulerBinding (помните такое?) для запроса к Flutter Engine, чтобы тот запланировал новый кадр.

Это именно тот момент, когда Flutter Engine «будит» SchedulerBinding и происходит вся магия.

onDrawFrame()

Ранее в этой статье мы отметили, что у SchedulerBinding две основные обязанности, одна из которых заключается в готовности обрабатывать запросы, создаваемые Flutter Engine, связанные с перестроением кадра. Это идеальный момент, чтобы сосредоточиться на этом.

Ниже на частичной диаграмме последовательности показано, что происходит, когда SchedulerBinding получает запрос onDrawFrame() от Flutter Engine.

Шаг 1. Элементы

Вызывается WidgetsBinding, и данная привязка сначала рассматривает изменения, связанные с элементами. WidgetsBinding вызывает метод buildScope объекта buildOwner, так как BuildOwner отвечает за обработку дерева элементов. Этот метод проходит по списку dirty элементов и запрашивает их перестроение (rebuild).

Основными принципами данного метода-перестроения ( rebuild() ) являются:

Следующая анимация попытается сделать это объяснение немного нагляднее.

Примечание по виджетам и элементам

Для нового виджета создаётся элемент конкретного типа, соответствущего категории виджета, а именно:

У каждого из этих типов элементов есть свое собственное поведение. Например:

Шаг 2. renderObjects

Теперь после завершения всех действий, связанных с dirty элементами, Element Tree является стабильным. Поэтому пришло время рассмотреть процесс визуализации.

Поскольку RendererBinding отвечает за обработку Render Tree, WidgetsBinding вызывает метод drawFrame RendererBinding.

Ниже на частичной диаграмме показана последовательность действий, выполняемых во время запроса drawFrame().

На этом шаге выполняются следующие действия:

В конце этого потока действий экран устройства обновляется.

Часть 3: Обработка жестов

Жесты (= события, связанные с действиями пальца на стекле) обрабатываются с помощью GestureBinding.

Когда Flutter Engine отправляет информацию о событии, связанном с жестом, через window.onPointerDataPacket API, то GestureBinding перехватывает её, выполняет некоторую буферизацию и:

Надеюсь, сейчас понятно, насколько важны renderObjects.

Часть 4: Анимации

Эта часть статьи посвящена понятию анимации и глубокому пониманию Ticker.

Когда вы работаете с анимациями, то вы обычно используете AnimationController или любой виджет для анимаций (прим: AnimatedCrossFade).

Во Flutter всё, что связано с анимациями, относится к Ticker. У Ticker, когда он активен, есть только одна задача: «он просит SchedulerBinding зарегистрировать обратный вызов и сообщить Flutter Engine, что надо разбудить его, когда появится новый обратный вызов». Когда Flutter Engine готов, он вызывает SchedulerBinding через запрос: «onBeginFrame«. SchedulerBinding обращается к списку обратных вызовов ticker и выполняет каждый из них.

Каждый tick перехватывается «заинтересованным» контроллером для его обработки. Если анимация завершена, то ticker «отключён», иначе ticker запрашивает SchedulerBinding для планирования нового обратного вызова. И так далее.

Полная картина

Теперь мы узнали, как работает Flutter:

BuildContext

Напоследок вернёмся к диаграмме, которая показывает различные типы элементов, и рассмотрим сигнатуру корневого Element:

Мы видим тот самый всем известный BuildContext! Но что это такое?

BuildContext — это интерфейс, определяющий ряд геттеров и методов, которые могут быть реализованы элементом. В основном BuildContext используется в методе build() StatelessWidget или State для StatefulWidget.

Это означает, что большинство разработчиков постоянно работают с элементами, даже не зная об этом.

Насколько полезным может быть BuildContext?

Поскольку BuildContext соответствует элементу, связанному с виджетом, а также местоположению виджета в дереве, то BuildContext может быть полезен, когда надо:

Забавы ради

ПРЕДУПРЕЖДЕНИЕ
Пожалуйста, не используйте этот код!

Его единственная задача – продемонстрировать, что StatelessWidget может запрашивать обновление.
Если вам нужно некоторое состояние с виджетом, пожалуйста, используйте StatefulWidget.

Заключение

Вы скажете: «Ещё одна длинная статья». Но я подумал, что вам было бы интересно узнать, как построена архитектура Flutter, и решил напомнить, что всё было разработано, чтобы быть эффективным, масштабируемым и открытым для будущих расширений. Кроме того, ключевые понятия, такие как Widget, Element, BuildContext, RenderObject, не всегда очевидны для восприятия. Могу только надеяться, что эта статья была для вас полезной.

Ждите новых новостей уже скоро. А пока позвольте пожелать вам успешного программирования.

PS Всю критику, вопросы и предложения по переводу буду рад услышать в (личных) сообщениях.
PSS Ещё раз ссылка на оригинальную статью Flutter internals от Didier Boelens, так как одной в шапке перевода для такого большого материала мало)

Источник

Сказочный портал