RenderObject Tree — to trzeci poziom hierarchii we Flutter, odpowiedzialny za faktyczne renderowanie interfejsu na ekranie. Każdy węzeł tego drzewa wykonuje obliczanie rozmiarów (layout) i rysowanie (painting), przekształcając konfigurację widgetów w piksele. W przeciwieństwie do Widget Tree, RenderObject Tree jest tworzony raz i aktualizowany tylko przy zmianie geometrii lub wyglądu. Według danych Flutter API Reference, 2025, wydajność RenderObject Tree bezpośrednio określa płynność animacji i szybkość reakcji aplikacji.
Najważniejsze
RenderObject Tree — to hierarchiczna struktura obiektów renderowania, która jest faktycznym reprezentacją interfejsu w pamięci. Każdy RenderObject zna swoje rozmiary, położenie na ekranie i potrafi się narysować. W przeciwieństwie do widgetów, które są lekkie i mogą być odtwarzane setki razy na sekundę, RenderObject — to ciężki obiekt z bezpośrednim dostępem do potoku graficznego.
Ścieżka od widgetu do pikseli przechodzi trzy etapy: Widget Tree opisuje, co powinno być na ekranie; Element Tree zarządza cyklem życia; RenderObject Tree wykonuje faktyczną pracę. Każdy RenderObjectWidget (np. Padding, Transform, CustomPaint) tworzy RenderObject, który jest dodawany do drzewa renderowania. RenderBox — najczęściej używany typ RenderObject, stosowany dla 99% standardowych widgetów.
RenderObject — to abstrakcyjna klasa definiująca interfejs dla layout i paint. Zawiera odniesienia do nadrzędnego RenderObject i obiektów potomnych, a także abstrakcyjne metody: performLayout, paint i hitTest. Każdy konkretny RenderObject implementuje te metody zgodnie ze swoim zachowaniem: RenderFlex rozdziela przestrzeń między elementami potomnymi, RenderImage wyświetla obraz, RenderParagraph wyświetla tekst.
abstract class RenderObject {
RenderObject? parent;
Constraints constraints;
ParentData? parentData;
bool _needsLayout = true;
bool _needsPaint = true;
void performLayout();
void paint(PaintingContext context, Offset offset);
}
W tej uproszczonej strukturze RenderObject zawiera flagi _needsLayout i _needsPaint, które oznaczają obiekt jako wymagający aktualizacji. Gdy Widget Tree się zmienia, Element Tree oznacza odpowiednie RenderObject jako „brudne„ dla layout lub paint, a przy następnej klatce Flutter wykonuje tylko niezbędne operacje.
Każdy RenderObject wykonuje dwie kluczowe fazy: layout (określanie rozmiarów i pozycji) oraz paint (rysowanie). Te fazy są wykonywane w ścisłej kolejności: najpierw layout dla całego drzewa, potem paint. Faza paint może zostać pominięta, jeśli layout się nie zmienił, co oszczędza zasoby GPU.
Podczas layout Flutter przekazuje ograniczenia (constraints) z góry na dół przez RenderObject Tree. Każdy rodzic określa dla obiektu potomnego minimalną i maksymalną szerokość oraz wysokość. Obiekt potomny oblicza swój rozmiar w ramach tych ograniczeń i zwraca go rodzicowi. Ten proces nazywa się „downward pass„ (przekazywanie ograniczeń w dół) i „upward pass„ (przekazywanie rozmiarów w górę).
Po zakończeniu layout Flutter wykonuje paint — rysowanie każdego RenderObject. Rodzic tworzy PaintingContext, przekazuje go obiektom potomnym i określa przesunięcie (offset). Każdy RenderObject rysuje siebie na płótnie Canvas, używając prymitywów graficznych: prostokąty, koła, tekst, obrazy i transformacje. Canvas bezpośrednio współdziała z Skia lub Impeller.
Flutter używa mechanizmu „brudnych„ węzłów do minimalizacji pracy. Gdy zmienia się tylko rozmiar jednego RenderObject, Flutter nie przelicza layoutu całego drzewa — oznacza zmieniony węzeł i jego potencjalnych przodków aż do najbliższego RenderBox o stałym rozmiarze. Podobnie, przy zmianie wyglądu oznaczany jest tylko paint, bez ponownego layoutu.
Flutter udostępnia kilka kategorii RenderObject do różnych zadań: RenderBox dla standardowych prostokątnych elementów, RenderSliver dla obszarów przewijanych oraz niestandardowe RenderObject do nietypowego renderowania przez CustomPainter. Każdy typ jest zoptymalizowany do swojej roli w drzewie renderowania.
RenderBox — klasa bazowa dla prostokątnych elementów interfejsu. Jego podtypy: RenderPadding (dodaje odstępy), RenderTransform (stosuje transformacje), RenderFlex (implementuje Row i Column), RenderStack (nakładanie elementów), RenderImage (wyświetlanie obrazów), RenderParagraph (wyświetlanie tekstu). Każdy podtyp nadpisuje performLayout i paint dla własnej logiki.
RenderSliver — to typ RenderObject do pracy z obszarami przewijanymi. W przeciwieństwie do RenderBox, Sliver używa SliverConstraints, które zawierają informacje o widocznym obszarze (viewport). RenderSliverList i RenderSliverGrid tworzą RenderObject tylko dla elementów znajdujących się w widocznym obszarze, co pozwala na obsługę list z milionami rekordów.
Do niestandardowej grafiki użyj CustomPaint i CustomPainter. CustomPaint tworzy RenderCustomPaint, który wywołuje metody paint w CustomPainter. Pozwala to na rysowanie dowolnych kształtów, wykresów i animacji z pełną kontrolą nad Canvas. Według Flutter Team, CustomPainter jest bardziej wydajny niż zagnieżdżone standardowe widgety dla złożonej grafiki wektorowej.
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;
}
W tym przykładzie CirclePainter rysuje okrąg na Canvas. Metoda shouldRepaint zwraca true tylko przy zmianie koloru, co zapobiega niepotrzebnemu przerysowaniu. CustomPainter powinien być maksymalnie lekki — wszystkie ciężkie obliczenia wykonuj poza metodami paint.
RenderObject Tree jest tworzony z Element Tree poprzez mechanizm RenderObjectWidget. Każdy RenderObjectWidget (Padding, Transform, CustomPaint) tworzy RenderObjectElement, który tworzy i zarządza odpowiednim RenderObject. Element pełni rolę pośrednika: przekazuje konfigurację z widgetu do RenderObject i powiadamia RenderObject o zmianach.
Gdy RenderObjectElement jest montowany, wywołuje metodę createRenderObject swojego widgetu. Widget tworzy instancję RenderObject i zwraca ją elementowi. Element osadza RenderObject w RenderObject Tree, wywołując metodę insertChildLayout u nadrzędnego RenderObject. Ten proces zachodzi tylko przy pierwszym montowaniu — przy kolejnych aktualizacjach element po prostu aktualizuje parametry istniejącego RenderObject.
Gdy konfiguracja widgetu się zmienia (np. zmienia się wartość padding), element wywołuje metodę updateRenderObject, która przekazuje nową konfigurację istniejącemu RenderObject. RenderObject oznacza siebie jako „brudny„ dla layout lub paint, a przy następnej klatce framework wykonuje niezbędne aktualizacje.
Przy demontażu elementu wywoływana jest metoda unmount, która usuwa RenderObject z RenderObject Tree i zwalnia zasoby. RenderObject.remove jest wywoływany w celu odłączenia od rodzica, po czym obiekt może zostać zebrany przez garbage collector. Flutter gwarantuje, że RenderObject nie wisi w drzewie bez odpowiadającego mu elementu.
RepaintBoundary — to widget, który tworzy oddzielną warstwę do renderowania swojej zawartości. Gdy zawartość RepaintBoundary się zmienia, przerysowywana jest tylko ta warstwa, a pozostałe części ekranu pozostają bez zmian. RepaintBoundary jest szczególnie przydatny dla animacji, odtwarzaczy wideo, interaktywnych wykresów i innych często aktualizowanych elementów.
U podstaw RepaintBoundary leży RenderRepaintBoundary — specjalny RenderObject, który tworzy oddzielną warstwę PictureLayer. Przy pierwszym renderowaniu RenderRepaintBoundary zapisuje polecenia graficzne w tej warstwie. Przy kolejnych aktualizacjach, jeśli zmieniła się tylko zawartość wewnątrz RepaintBoundary, Flutter przerysowuje tylko tę warstwę, a nie cały ekran. Pozostałe warstwy pozostają niezmienione i są ponownie używane.
Nie każdy widget potrzebuje RepaintBoundary. Używaj go, gdy część interfejsu aktualizuje się z wysoką częstotliwością (60 FPS i więcej), a reszta ekranu jest statyczna. Typowe przykłady: animowany wskaźnik ładowania, odtwarzacz wideo, płótno gry, CustomPainter z częstym przerysowaniem. Dla statycznego tekstu lub przycisków RepaintBoundary jest zbędny i tylko zwiększa zużycie pamięci.
| Scenariusz | Zalecenie | Powód |
|---|---|---|
| Animacja | Opakować w RepaintBoundary | Izoluje często aktualizowany obszar |
| Lista przewijana | Nie wymagane | ListView automatycznie używa warstw |
| Tekst statyczny | Nie wymagane | Brak częstych przerysowań |
| CustomPainter | Zalecane | Częste przerysowanie grafiki |
Wydajność RenderObject Tree zależy od liczby węzłów, głębokości drzewa i częstotliwości przerysowań. Narzędzia Flutter DevTools (zakładka „Rendering„) pozwalają analizować RenderObject Tree w czasie rzeczywistym: liczba repaint, czas layout i paint, liczba warstw i ich rozmiary. Regularna analiza pomaga zidentyfikować wąskie gardła.
Overdraw — to sytuacja, gdy jeden piksel jest rysowany kilka razy na klatkę. Na przykład, gdy półprzezroczysty widget nakłada się na inny, GPU rysuje obie warstwy. Używaj przezroczystych flag (Container z color zamiast decoration) dla nieprzezroczystych elementów, aby Flutter pomijał niewidoczne warstwy. Według Flutter Team, zmniejszenie overdraw może skrócić czas paint do 30%.
Każdy RepaintBoundary i niektóre widgety (Opacity, ClipRRect, Transform) tworzą oddzielną warstwę (PictureLayer). Zbyt wiele warstw zwiększa czas kompozycji. Używaj grupowania: zamiast wielu Opacity na oddzielnych widgetach zastosuj jeden Opacity na kontenerze. Zamiast ClipRRect na każdym elemencie użyj ClipRRect na wspólnym kontenerze.
Gdy widget jest zadeklarowany jako const, Flutter wie, że jego konfiguracja się nie zmieni i może ponownie użyć odpowiedniego RenderObject bez odtwarzania. Konstruktory const zmniejszają obciążenie garbage collectora i przyspieszają pierwszą klatkę. Używaj const dla widgetów o stałych parametrach: ikony, nagłówki, elementy dekoracyjne.
const Text("Tytuł", style: TextStyle(fontSize: 24));
const Icon(Icons.home, color: Colors.blue);
const SizedBox.shrink();
Często zadawane pytania
Widget Tree — to lekka konfiguracja interfejsu, która jest odtwarzana przy każdym rebuild. RenderObject Tree — to ciężkie drzewo obiektów renderowania, które jest zachowywane i aktualizowane tylko przy zmianie geometrii lub wyglądu.
Użyj Flutter DevTools — zakładka „Rendering„. Zobaczysz warstwy renderowania, czas layout i paint, a także szczegółowe informacje o każdym RenderObject: rozmiary, constraints, flagi potrzeby aktualizacji.
Dirty — to RenderObject, który jest oznaczony jako wymagający aktualizacji. Flaga _needsLayout lub _needsPaint jest ustawiana przy zmianie konfiguracji, a Flutter wykonuje layout lub paint dla tego węzła w następnej klatce.
Tak, utwórz podklasę RenderBox i nadpisz metody performLayout oraz paint. Użyj RenderObjectWidget do osadzenia niestandardowego RenderObject w Widget Tree. To zaawansowana technika do niestandardowego renderowania.
RepaintBoundary tworzy RenderRepaintBoundary, który izoluje część RenderObject Tree w oddzielnej warstwie. Przy zmianie zawartości przerysowywana jest tylko ta warstwa, a reszta drzewa pozostaje bez zmian.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również