1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/CarGuo-GSYFlutterBook

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Flutter-9.md 16 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 10.03.2025 00:06 5767d61

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

Ссылка на полную серию статей:

Полная серия статей по Flutter

Серия статей "Мир Flutter"

В шестой и седьмой статьях мы узнали о взаимоотношении между Widget, Element и RenderObject, а также о логике размещения Widget. В конечном итоге все Widget преобразуются в объекты RenderObject, которые накладываются друг на друга, чтобы создать желаемое изображение.

Поэтому в Flutter окончательное размещение (layout) и отрисовка (paint) происходят в подклассах RenderObject, связанных с Widget. Один из ключевых моментов RenderObject — это то, что он является одной из основных особенностей Flutter, делающей его кросс-платформенным: все компоненты являются платформенно-независимыми, другими словами, Flutter требует лишь предоставляемого системой «Canvas», после чего разработчики могут использовать Widget для создания RenderObject, который затем прямым образом отрисовывается на экран через движок.

Примечание: с этого момента статья становится более объемной, поэтому может потребовать некоторого терпения со стороны читателя.## 1. Процесс отрисовки

Мы знаем, что все Widget в конце концов преобразуются в RenderObject, поэтому для понимания процесса отрисовки нам следует обратиться к методу paint объекта RenderObject.

На следующем рисунке показано, что все подклассы RenderObject должны реализовать метод paint, при этом этот метод не предназначен для прямого вызова пользователями; для обновления отрисовки можно использовать метод markNeedsPaint, чтобы запустить перерисовку интерфейса.

Тогда, согласно общепринятому порядку, после вычислений размеров и позиций, метод paint будет вызван, принимая два параметра: PaintingContext и Offset. Эти параметры являются ключевыми для завершения отрисовки, и здесь возникают вопросы:

  • Что такое PaintingContext?
  • Что такое Offset?

Быстрый просмотр исходного кода позволяет узнать, что:

  • Ключевой особенностью PaintingContext является то, что это место для отрисовки, и он содержит Canvas в родительском классе ClipContext. Конструктор PaintingContext помечен как @protected, и он автоматически создается только в методах repaintCompositedChild и pushLayer.
  • Offset в paint主要用于提供当前控件在屏幕上的相对偏移值,在绘制时确定绘制坐标的参考点。

image2

Хорошо, продолжим. Так как PaintingContext называется контекстом (context), то он, конечно же, имеет отношение к контексту. Но где именно он создается?По отладке исходного кода известно, что проект запускается через WidgetsFlutterBinding, когда вызывается метод runApp. В предыдущих разделах мы уже знали, что WidgetsFlutterBinding является "клеящим" классом, который активирует RendererBinding с помощью mixin, тем самым создает корневой узел PaintingContext.

image3

Теперь вопрос: а Offset? Как показано на следующем рисунке, передача Offset осуществляется путем суммирования offset родительского и дочернего компонентов, после чего координаты для рисования передаются уровнем ниже.

Проще говоря, через PaintingContext и Offset мы можем точно определить место на экране для рисования нужной графики после выполнения размещения.

image4

1. Тестирование рисования

Давайте начнем с интересного теста.

На нашем экране ограничим высоту зеленого контейнера Container значением 60 пикселей. В данный момент игнорируем контроллер Slider внутри контейнера. Мы рисуем красный квадрат размером 100x100 пикселей, но видим, что результат выглядит так: "Нань?" Почему такой маленький?

Факт заключается в том, что при обычном рисовании Container в Flutter, AppBar уже рассчитывает высоту состояния и заголовка, поэтому при использовании Canvas и прямого рисования drawRect, красный квадрат начинает отрисовываться с точки (0, 0), которая фактически находится в области состояния.image5

А если мы будем менять положение? Например, если установим вершину квадрата на уровень 300, то получим следующий результат: "Юань?" Красный квадрат выходит за границы контейнера.

image6

Здесь проблема связана с параметром estimatedBounds в PaintingContext. Обычно этот параметр инициализируется значением child.paintBounds при создании, однако есть описание этого параметра: Оказывается, можно рисовать за пределами контейнера.

Канвас позволяет рисовать за пределами этих границ.
Прямоугольник [estimatedBounds] находится в системе координат [canvas].

До этого момента можно сделать простое заключение: для Flutter весь экран является одним большим холстом, мы используем различные Offset и Rect, чтобы определить положение, а затем с помощью PaintingContext и Canvas рисуем на этом холсте целый экран. Каждое изменение приводит к полной перерисовке всего экрана.

2. RepaintBoundary

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

Помните метод markNeedsPaint? Начнем с него и выведем основной процесс, как показано ниже. Можно видеть, что markNeedsPaint действительно вызывает обновление отрисовки при выполнении requestVisualUpdate.

Основной процесс отрисовкиПерейдем к исходному коду. Как видно из кода, когда вызывается markNeedsPaint(), RenderObject начинает поиск среди родителей, определяя, начинать ли отсюда обновление отрисовки на основе значения isRepaintBoundary. Другими словами, это определение областей для обновления.

Поэтому фактический процесс следующий: через isRepaintBoundary определяется область для обновления, а метод requestVisualUpdate запускает обновление и передает его для отрисовки вниз по дереву компонентов.

Процесс markNeedsPaint

Из исходного кода также следует, что isRepaintBoundary имеет только метод get, поэтому он может быть переопределен только дочерними классами, указывающими, являются ли они границами для перерисовки. Например, isRepaintBoundary для RenderProxyBox, RenderView и RenderFlow всегда истинно.

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

3. Layer

Как известно из вышеописанного, если isRepaintBoundary истинно, то эта область становится областью для обновления отрисовки, и при создании этой области создается новый Layer.

Разные Layer могут работать независимо друг от друга, например, OffsetLayer используется в RenderObject для позиционирования и отрисовки.

Это также приводит к выводу: не все RenderObject имеют Layer, так как это зависит от значения isRepaintBoundary. ```Далее в RenderObject есть ещё одно свойство — `needsCompositing`, которое влияет на количество создаваемых слоёв `Layer`, а эти `Layer` составляют дерево Layer Tree. Ну что ж, теперь у нас есть ещё одно дерево, но это именно то дерево, которое используется для отрисовки движком.

На этом мы более-менее понимаем весь процесс отрисовки RenderObject. И этот момент отрисовки мы активируем, а не вызываем его явно, и обновление происходит при проверке области. Да, немного похоже на React!

Второй раздел: Реализация отрисовки компонента Slider

После того как мы рассмотрели все процессы отрисовки, давайте взглянем на реализацию отрисовки компонента Slider через его исходный код.

Структура всего компонента Slider очень типична для Flutter. Основная структура представлена на следующей схеме.

В _RenderSlider помимо жестов и анимаций, каждый элемент отрисовки является независимым component, который выполняет свою работу. Все эти components предоставляются через SliderTheme и SliderThemeData. ```Кстати, сам SliderTheme является `InheritedWidget`. Те, кто читали предыдущие статьи, должны знать, что `InheritedWidget` обычно используют для передачи состояния между виджетами. Поэтому если вы хотите настроить `Slider`, вы можете использовать `SliderTheme` для вложения и затем настраивать нужные вам параметры через `SliderThemeData`.

Кроме того, в _RenderSlider регистрируются жесты и анимации, которые активируют метод markNeedsPaint при получении события, что объясняет реакцию вашего прикосновения на экране.

Также можно заметить, что все параметры внутри _SliderRender переопределяют методы get и set. При установке значения также вызывается markNeedsPaint() или _updateLabelPainter для косвенной активации markNeedsLayout.

image.png

Что касается различных фигур в Slider, их отрисовка осуществляется стандартными методами Canvas: saveLayer, drawRect, translate, drawPath и так далее.

Так заканчивается девятая глава! (///▽///)

Рекомендованные ресурсы

Рекомендованные полные открытые проекты:

Увидимся еще?

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/CarGuo-GSYFlutterBook.git
git@api.gitlife.ru:oschina-mirror/CarGuo-GSYFlutterBook.git
oschina-mirror
CarGuo-GSYFlutterBook
CarGuo-GSYFlutterBook
master