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 се извиква за отделяне от родителя, след което обектът може да бъде събран от garbage collector-а. 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-конструкторите намаляват натоварването на garbage collector-а и ускоряват първия кадър. Използвайте const за уиджети с фиксирани параметри: икони, заглавия, декоративни елементи.
const Text("Заглавие", 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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също