RenderObject Tree — это третий уровень иерархии во Flutter, отвечающий за фактическую отрисовку интерфейса на экране. Каждый узел этого дерева выполняет вычисление размеров (layout) и рисование (painting), преобразуя конфигурацию виджетов в пиксели. В отличие от Widget Tree, RenderObject Tree создаётся один раз и обновляется только при изменении геометрии или внешнего вида. По данным Flutter API Reference, 2025, эффективность RenderObject Tree напрямую определяет плавность анимаций и скорость отклика приложения.
Главное
RenderObject Tree — это иерархическая структура объектов отрисовки, которая является фактическим представлением интерфейса в памяти. Каждый RenderObject знает свои размеры, положение на экране и умеет рисовать себя. В отличие от виджетов, которые легковесны и могут пересоздаваться сотни раз в секунду, RenderObject — это тяжёлый объект с непосредственным доступом к графическому конвейеру.
Путь от виджета к пикселям проходит три этапа: Widget Tree описывает, что должно быть на экране; Element Tree управляет жизненным циклом; RenderObject Tree выполняет фактическую работу. Каждый RenderObjectWidget (например, Padding, Transform, CustomPaint) создаёт RenderObject, который добавляется в дерево отрисовки. RenderBox — наиболее распространённый тип RenderObject, используемый для 99% стандартных виджетов.
RenderObject — это абстрактный класс, определяющий интерфейс для layout и paint. Он содержит ссылки на родительский RenderObject и дочерние объекты, а также абстрактные методы: performLayout, paint и hitTest. Каждый конкретный RenderObject реализует эти методы в соответствии со своим поведением: RenderFlex распределяет пространство между дочерними элементами, RenderImage отображает изображение, RenderParagraph выводит текст.
abstract class RenderObject {
RenderObject? parent;
Constraints constraints;
ParentData? parentData;
bool _needsLayout = true;
bool _needsPaint = true;
void performLayout();
void paint(PaintingContext context, Offset offset);
}
В этой упрощённой структуре RenderObject содержит флаги _needsLayout и _needsPaint, которые отмечают объект как требующий обновления. Когда Widget Tree изменяется, Element Tree помечает соответствующие RenderObject как "грязные" для layout или paint, и при следующем кадре Flutter выполняет только необходимые операции.
Каждый RenderObject выполняет две ключевые фазы: layout (определение размеров и позиций) и paint (рисование). Эти фазы выполняются в строгом порядке: сначала layout для всего дерева, затем paint. Фаза paint может быть пропущена, если layout не изменился, что экономит ресурсы GPU.
Во время layout Flutter передаёт ограничения (constraints) сверху вниз по RenderObject Tree. Каждый родитель задаёт дочернему объекту минимальную и максимальную ширину и высоту. Дочерний объект вычисляет свой размер в пределах этих ограничений и возвращает его родителю. Этот процесс называется "downward pass" (передача ограничений вниз) и "upward pass" (передача размеров вверх).
После завершения layout Flutter выполняет paint — рисование каждого RenderObject. Родитель создаёт PaintingContext, передаёт его дочерним объектам и указывает смещение (offset). Каждый RenderObject рисует себя на холсте Canvas, используя графические примитивы: прямоугольники, круги, текст, изображения и трансформации. Canvas напрямую взаимодействует с Skia или Impeller.
Flutter использует механизм "грязных" узлов для минимизации работы. Когда изменяется только размер одного RenderObject, Flutter не пересчитывает layout всего дерева — он помечает изменившийся узел и его возможных предков до ближайшего RenderBox с фиксированным размером. Аналогично, при изменении внешнего вида помечается только paint, без повторного layout.
Flutter предоставляет несколько категорий RenderObject для разных задач: RenderBox для стандартных прямоугольных элементов, RenderSliver для прокручиваемых областей и кастомные RenderObject для нестандартной отрисовки через CustomPainter. Каждый тип оптимизирован для своей роли в дереве отрисовки.
RenderBox — базовый класс для прямоугольных элементов интерфейса. Его подтипы: RenderPadding (добавляет отступы), RenderTransform (применяет трансформации), RenderFlex (реализует Row и Column), RenderStack (наложение элементов), RenderImage (отображение изображений), RenderParagraph (отображение текста). Каждый подтип переопределяет performLayout и paint для своей логики.
RenderSliver — это тип RenderObject для работы с прокручиваемыми областями. В отличие от RenderBox, Sliver использует SliverConstraints, которые включают информацию о видимой области (viewport). RenderSliverList и RenderSliverGrid создают RenderObject только для элементов, попадающих в видимую область, что позволяет обрабатывать списки с миллионами записей.
Для нестандартной графики используйте CustomPaint и CustomPainter. CustomPaint создаёт RenderCustomPaint, который вызывает методы paint у CustomPainter. Это позволяет рисовать произвольные фигуры, графики и анимации с полным контролем над Canvas. По данным Flutter Team, CustomPainter эффективнее вложенных стандартных виджетов для сложной векторной графики.
class CirclePainter extends CustomPainter {
final Color color;
CirclePainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color;
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
size.width / 3,
paint,
);
}
@override
bool shouldRepaint(CirclePainter oldDelegate) =>
oldDelegate.color != color;
}
В этом примере CirclePainter рисует круг на Canvas. Метод shouldRepaint возвращает true только при изменении цвета, что предотвращает ненужную перерисовку. CustomPainter должен быть максимально лёгким — все тяжёлые вычисления выполняйте вне методов paint.
RenderObject Tree создаётся из Element Tree через механизм RenderObjectWidget. Каждый RenderObjectWidget (Padding, Transform, CustomPaint) порождает RenderObjectElement, который создаёт и управляет соответствующим RenderObject. Элемент выступает в роли посредника: он передаёт конфигурацию из виджета в RenderObject и уведомляет RenderObject об изменениях.
Когда RenderObjectElement монтируется, он вызывает метод createRenderObject у своего виджета. Виджет создаёт экземпляр RenderObject и возвращает его элементу. Элемент встраивает RenderObject в RenderObject Tree, вызывая метод insertChildLayout у родительского RenderObject. Этот процесс происходит только при первом монтировании — при последующих обновлениях элемент просто обновляет параметры существующего RenderObject.
Когда конфигурация виджета изменяется (например, меняется значение padding), элемент вызывает метод updateRenderObject, который передаёт новую конфигурацию существующему RenderObject. RenderObject помечает себя как "грязный" для layout или paint, и при следующем кадре фреймворк выполняет необходимые обновления.
При демонтировании элемента вызывается метод unmount, который удаляет RenderObject из RenderObject Tree и освобождает ресурсы. RenderObject.remove называется для отсоединения от родителя, после чего объект может быть собран сборщиком мусора. Flutter гарантирует, что RenderObject не висит в дереве без соответствующего элемента.
RepaintBoundary — это виджет, который создаёт отдельный слой для отрисовки своего содержимого. Когда содержимое RepaintBoundary изменяется, перерисовывается только этот слой, а остальные части экрана остаются без изменений. RepaintBoundary особенно полезен для анимаций, видеоплееров, интерактивных графиков и других часто обновляемых элементов.
В основе RepaintBoundary лежит RenderRepaintBoundary — специальный RenderObject, который создаёт отдельный PictureLayer. При первой отрисовке RenderRepaintBoundary записывает графические команды в этот слой. При последующих обновлениях, если изменилось только содержимое внутри RepaintBoundary, Flutter перерисовывает только этот слой, а не весь экран. Остальные слои остаются неизменными и используются повторно.
Не каждый виджет нуждается в RepaintBoundary. Используйте его, когда часть интерфейса обновляется с высокой частотой (60 FPS и выше), а остальной экран статичен. Типичные примеры: анимированный индикатор загрузки, видеоплеер, игровой Canvas, CustomPainter с частой перерисовкой. Для статического текста или кнопок RepaintBoundary избыточен и только увеличивает потребление памяти.
| Сценарий | Рекомендация | Причина |
|---|---|---|
| Анимация | Оборачивать в RepaintBoundary | Изолирует часто обновляемую область |
| Список с прокруткой | Не требуется | ListView использует слои автоматически |
| Статичный текст | Не требуется | Нет частых перерисовок |
| CustomPainter | Рекомендуется | Частая перерисовка графики |
Производительность RenderObject Tree зависит от количества узлов, глубины дерева и частоты перерисовок. Инструменты Flutter DevTools (вкладка "Rendering") позволяют анализировать RenderObject Tree в реальном времени: количество repaint, время layout и paint, количество слоёв и их размеры. Регулярный анализ помогает выявить узкие места.
Overdraw — это ситуация, когда один пиксель рисуется несколько раз за кадр. Например, когда полупрозрачный виджет накладывается на другой, GPU рисует оба слоя. Используйте opaque флаги (Container с color вместо decoration) для непрозрачных элементов, чтобы Flutter пропускал невидимые слои. По данным Flutter Team, уменьшение overdraw может сократить время paint до 30%.
Каждый RepaintBoundary и некоторые виджеты (Opacity, ClipRRect, Transform) создают отдельный слой (PictureLayer). Слишком много слоёв увеличивает время композиции. Используйте группировку: вместо нескольких Opacity на отдельных виджетов примените один Opacity на контейнер. Вместо ClipRRect на каждом элементе используйте ClipRRect на общем контейнере.
Когда виджет объявлен как const, Flutter знает, что его конфигурация не изменится, и может переиспользовать соответствующий RenderObject без пересоздания. Const-конструкторы снижают нагрузку на сборщик мусора и ускоряют первый кадр. Используйте const для виджетов с фиксированными параметрами: иконки, заголовки, декоративные элементы.
const Text("Title", style: TextStyle(fontSize: 24));
const Icon(Icons.home, color: Colors.blue);
const SizedBox.shrink();
Часто задаваемые вопросы
Widget Tree — это лёгкая конфигурация интерфейса, которая пересоздаётся при каждом rebuild. RenderObject Tree — это тяжёлое дерево объектов отрисовки, которое сохраняется и обновляется только при изменении геометрии или внешнего вида.
Используйте Flutter DevTools — вкладка "Rendering". Вы увидите слои отрисовки, время layout и paint, а также детальную информацию о каждом RenderObject: размеры, constraints, флаги потребности в обновлении.
Dirty — это RenderObject, который помечен как требующий обновления. Флаг _needsLayout или _needsPaint устанавливается при изменении конфигурации, и Flutter выполняет layout или paint для этого узла в следующем кадре.
Да, создайте подкласс RenderBox и переопределите методы performLayout и paint. Используйте RenderObjectWidget для встраивания кастомного RenderObject в Widget Tree. Это продвинутая техника для нестандартной отрисовки.
RepaintBoundary создаёт RenderRepaintBoundary, который изолирует часть RenderObject Tree в отдельный слой. При изменении содержимого перерисовывается только этот слой, а остальная часть дерева остаётся без изменений.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также