RenderObject Tree è il terzo livello della gerarchia di Flutter, responsabile del rendering effettivo dell'interfaccia sullo schermo. Ogni nodo di questo albero esegue il calcolo delle dimensioni (layout) e il disegno (painting), convertendo la configurazione dei widget in pixel. A differenza del Widget Tree, il RenderObject Tree viene creato una volta e aggiornato solo quando cambiano la geometria o l'aspetto. Secondo il Riferimento API Flutter, 2025, l'efficienza del RenderObject Tree determina direttamente la fluidità delle animazioni e la reattività dell'applicazione.
Punti chiave
RenderObject Tree è una struttura gerarchica di oggetti di rendering che funge da rappresentazione effettiva dell'interfaccia in memoria. Ogni RenderObject conosce le proprie dimensioni, la posizione sullo schermo e come disegnarsi. A differenza dei widget, che sono leggeri e possono essere ricreati centinaia di volte al secondo, un RenderObject è un oggetto pesante con accesso diretto alla pipeline grafica.
Il percorso dal widget ai pixel passa attraverso tre fasi: il Widget Tree descrive ciò che deve essere sullo schermo; l'Element Tree gestisce il ciclo di vita; il RenderObject Tree esegue il lavoro effettivo. Ogni RenderObjectWidget (come Padding, Transform, CustomPaint) crea un RenderObject che viene aggiunto all'albero di rendering. RenderBox è il tipo più comune di RenderObject, utilizzato dal 99% dei widget standard.
RenderObject è una classe astratta che definisce l'interfaccia per layout e paint. Contiene riferimenti al RenderObject padre e agli oggetti figli, nonché metodi astratti: performLayout, paint e hitTest. Ogni RenderObject concreto implementa questi metodi secondo il proprio comportamento: RenderFlex distribuisce lo spazio tra i figli, RenderImage visualizza un'immagine, RenderParagraph rende il testo.
abstract class RenderObject {
RenderObject? parent;
Constraints constraints;
ParentData? parentData;
bool _needsLayout = true;
bool _needsPaint = true;
void performLayout();
void paint(PaintingContext context, Offset offset);
}
In questa struttura semplificata, RenderObject contiene i flag _needsLayout e _needsPaint, che marcano l'oggetto come bisognoso di aggiornamento. Quando il Widget Tree cambia, l'Element Tree marca i RenderObject corrispondenti come "sporchi" per layout o paint, e al frame successivo Flutter esegue solo le operazioni necessarie.
Ogni RenderObject esegue due fasi principali: layout (determinazione di dimensioni e posizioni) e paint (disegno). Queste fasi vengono eseguite in ordine rigoroso: prima layout per l'intero albero, poi paint. La fase di paint può essere saltata se il layout non è cambiato, risparmiando risorse della GPU.
Durante il layout, Flutter passa vincoli (constraints) dall'alto verso il basso attraverso il RenderObject Tree. Ogni genitore definisce larghezza e altezza minima e massima per il proprio figlio. Il figlio calcola le proprie dimensioni all'interno di questi vincoli e le restituisce al genitore. Questo processo è chiamato "downward pass" (passaggio dei vincoli verso il basso) e "upward pass" (passaggio delle dimensioni verso l'alto).
Dopo aver completato il layout, Flutter esegue il paint — disegno di ogni RenderObject. Il genitore crea un PaintingContext, lo passa agli oggetti figli e specifica l'offset. Ogni RenderObject si disegna sulla Canvas utilizzando primitive grafiche: rettangoli, cerchi, testo, immagini e trasformazioni. La Canvas interagisce direttamente con Skia o Impeller.
Flutter utilizza un meccanismo di nodi "sporchi" per minimizzare il lavoro. Quando solo la dimensione di un RenderObject cambia, Flutter non ricalcola il layout dell'intero albero — marca il nodo modificato e i suoi possibili antenati fino al RenderBox più vicino con dimensione fissa. Analogamente, quando l'aspetto cambia, viene marcato solo paint senza rifare il layout.
Flutter fornisce diverse categorie di RenderObject per compiti differenti: RenderBox per elementi rettangolari standard, RenderSliver per aree scorrevoli e RenderObject personalizzati per rendering non standard tramite CustomPainter. Ogni tipo è ottimizzato per il proprio ruolo nell'albero di rendering.
RenderBox è la classe base per gli elementi di interfaccia rettangolari. I suoi sottotipi includono: RenderPadding (aggiunge padding), RenderTransform (applica trasformazioni), RenderFlex (implementa Row e Column), RenderStack (sovrappone elementi), RenderImage (visualizza immagini), RenderParagraph (rende il testo). Ogni sottotipo sovrascrive performLayout e paint per la propria logica.
RenderSliver è un tipo di RenderObject per lavorare con aree scorrevoli. A differenza di RenderBox, Sliver utilizza SliverConstraints, che includono informazioni sul viewport. RenderSliverList e RenderSliverGrid creano RenderObject solo per gli elementi nell'area visibile, consentendo di gestire elenchi con milioni di voci.
Per grafiche non standard, utilizzare CustomPaint e CustomPainter. CustomPaint crea RenderCustomPaint, che chiama i metodi paint di CustomPainter. Ciò consente di disegnare forme arbitrarie, grafici e animazioni con controllo completo sulla Canvas. Secondo il team di Flutter, CustomPainter è più efficiente dei widget standard annidati per la grafica vettoriale complessa.
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;
}
In questo esempio, CirclePainter disegna un cerchio sulla Canvas. Il metodo shouldRepaint restituisce true solo quando il colore cambia, prevenendo ripainting non necessari. CustomPainter dovrebbe essere il più leggero possibile — esegui tutti i calcoli pesanti al di fuori dei metodi paint.
RenderObject Tree viene creato dall'Element Tree attraverso il meccanismo RenderObjectWidget. Ogni RenderObjectWidget (Padding, Transform, CustomPaint) crea un RenderObjectElement, che a sua volta crea e gestisce il RenderObject corrispondente. L'elemento funge da intermediario: passa la configurazione dal widget al RenderObject e notifica il RenderObject dei cambiamenti.
Quando un RenderObjectElement viene montato, chiama il metodo createRenderObject sul suo widget. Il widget crea un'istanza di RenderObject e la restituisce all'elemento. L'elemento inserisce il RenderObject nel RenderObject Tree chiamando il metodo insertChildLayout sul RenderObject padre. Questo processo avviene solo al primo montaggio — negli aggiornamenti successivi, l'elemento aggiorna semplicemente i parametri del RenderObject esistente.
Quando la configurazione del widget cambia (ad esempio, il valore di padding cambia), l'elemento chiama il metodo updateRenderObject, che passa la nuova configurazione al RenderObject esistente. Il RenderObject si marca come "sporco" per layout o paint, e al frame successivo il framework esegue gli aggiornamenti necessari.
Quando un elemento viene smontato, viene chiamato il metodo unmount, che rimuove il RenderObject dal RenderObject Tree e libera le risorse. RenderObject.remove viene chiamato per scollegarsi dal genitore, dopodiché l'oggetto può essere raccolto dal garbage collector. Flutter garantisce che nessun RenderObject rimanga sospeso nell'albero senza un elemento corrispondente.
RepaintBoundary è un widget che crea un layer separato per il rendering del suo contenuto. Quando il contenuto all'interno di un RepaintBoundary cambia, solo quel layer viene ripainted, mentre il resto dello schermo rimane invariato. RepaintBoundary è particolarmente utile per animazioni, lettori video, grafici interattivi e altri elementi aggiornati frequentemente.
Al centro di RepaintBoundary c'è RenderRepaintBoundary — un RenderObject speciale che crea un PictureLayer separato. Al primo rendering, RenderRepaintBoundary registra i comandi grafici in questo layer. Negli aggiornamenti successivi, se solo il contenuto all'interno del RepaintBoundary cambia, Flutter ripaints solo questo layer anziché l'intero schermo. Gli altri layer rimangono invariati e vengono riutilizzati.
Non tutti i widget hanno bisogno di RepaintBoundary. Usalo quando una parte dell'interfaccia si aggiorna ad alta frequenza (60 FPS o superiore) mentre il resto dello schermo è statico. Esempi tipici: un indicatore di caricamento animato, lettore video, Canvas di gioco, CustomPainter con ripainting frequenti. Per testo statico o pulsanti, RepaintBoundary è eccessivo e aumenta solo il consumo di memoria.
| Scenario | Raccomandazione | Motivo |
|---|---|---|
| Animazione | Avvolgere in RepaintBoundary | Isola l'area aggiornata frequentemente |
| Elenco scorrevole | Non necessario | ListView usa i layer automaticamente |
| Testo statico | Non necessario | Nessun ripainting frequente |
| CustomPainter | Raccomandato | Ripainting grafico frequente |
Le prestazioni del RenderObject Tree dipendono dal numero di nodi, dalla profondità dell'albero e dalla frequenza di ripainting. Flutter DevTools (la scheda "Rendering") consente di analizzare il RenderObject Tree in tempo reale: conteggio dei ripaint, tempo di layout e paint, numero di layer e loro dimensioni. L'analisi regolare aiuta a identificare i colli di bottiglia.
Overdraw è una situazione in cui un singolo pixel viene disegnato più volte per frame. Ad esempio, quando un widget semitrasparente si sovrappone a un altro, la GPU disegna entrambi i layer. Utilizza flag opachi (Container con color invece di decoration) per elementi non trasparenti, in modo che Flutter salti i layer invisibili. Secondo il team di Flutter, ridurre l'overdraw può diminuire il tempo di paint fino al 30%.
Ogni RepaintBoundary e alcuni widget (Opacity, ClipRRect, Transform) creano un layer separato (PictureLayer). Troppi layer aumentano il tempo di composizione. Utilizza il raggruppamento: invece di più widget Opacity su singoli elementi, applica una singola Opacity al contenitore. Invece di ClipRRect su ogni elemento, usa ClipRRect sul contenitore comune.
Quando un widget viene dichiarato come const, Flutter sa che la sua configurazione non cambierà e può riutilizzare il RenderObject corrispondente senza ricrearlo. I costruttori const riducono il carico del garbage collector e accelerano il primo frame. Usa const per widget con parametri fissi: icone, titoli, elementi decorativi.
const Text("Titolo", style: TextStyle(fontSize: 24));
const Icon(Icons.home, color: Colors.blue);
const SizedBox.shrink();
Domande frequenti
Widget Tree è una configurazione leggera dell'interfaccia che viene ricreata ad ogni ricostruzione. RenderObject Tree è un albero pesante di oggetti di rendering che persiste e viene aggiornato solo quando cambiano geometria o aspetto.
Usa Flutter DevTools — la scheda "Rendering". Vedrai i layer di rendering, i tempi di layout e paint, e informazioni dettagliate su ogni RenderObject: dimensioni, vincoli e flag sporchi.
Dirty è un RenderObject marcato come bisognoso di aggiornamento. Il flag _needsLayout o _needsPaint viene impostato quando la configurazione cambia, e Flutter esegue layout o paint per quel nodo al frame successivo.
Sì, crea una sottoclasse di RenderBox e sovrascrivi i metodi performLayout e paint. Usa RenderObjectWidget per incorporare un RenderObject personalizzato nel Widget Tree. Questa è una tecnica avanzata per rendering non standard.
RepaintBoundary crea RenderRepaintBoundary, che isola una parte del RenderObject Tree in un layer separato. Quando il contenuto cambia, solo quel layer viene ripainted, lasciando inalterato il resto dell'albero.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche