# Ottimizzazione delle prestazioni di Flutter nel 2026: Impeller, ricostruzioni e best practice > Come mantenere le app Flutter a 60 o 120 fps costanti nel 2026 con Impeller, ricostruzioni di widget disciplinate, RepaintBoundary e profiling con DevTools. - Published: 2026-07-04 - Updated: 2026-07-07 - Author: SharpSkill - Tags: flutter, performance, impeller, best-practices, dart, mobile - Reading time: 9 min --- L'ottimizzazione delle prestazioni di Flutter nel 2026 poggia su due cambiamenti che hanno ridefinito il modo di costruire app fluide: il motore di rendering Impeller è ora attivo per impostazione predefinita su iOS e Android, e il modello di ricostruzione del framework premia gli sviluppatori che mantengono gli alberi di widget piccoli e immutabili. I team che sfruttano entrambe le leve tengono un frame rate costante di 60 o 120 fps senza regolare ogni frame a mano. > **Il cambiamento a maggiore impatto** > > Il guadagno più grande nella maggior parte delle app Flutter consiste nell'aggiungere costruttori `const` e nel suddividere i metodi `build` di grandi dimensioni, così che una chiamata a `setState` ridisegni un solo pulsante invece di un'intera schermata. ## Perché Impeller ridefinisce la baseline delle prestazioni di Flutter Impeller è il motore di rendering di Flutter e ha sostituito Skia come impostazione predefinita sulle piattaforme mobili. La differenza conta soprattutto nei primi secondi di un'animazione. Skia compilava i suoi shader al volo, la prima volta che un effetto appariva sullo schermo, il che produceva il famigerato scatto al primo avvio per cui le app Flutter venivano criticate. Impeller compila quegli shader in anticipo, durante la fase di build, così che il primissimo frame di un'animazione sia fluido quanto il centesimo. Impeller punta inoltre direttamente alle API grafiche moderne: Metal su iOS e Vulkan su Android, con un ripiego su OpenGL per l'hardware Android più datato. La [documentazione del motore Impeller su GitHub](https://github.com/flutter/flutter/blob/master/docs/engine/impeller/README.md) descrive nel dettaglio l'architettura di rendering e la matrice di supporto delle piattaforme. | Aspetto | Skia (legacy) | Impeller (predefinito 2026) | |--------|---------------|-------------------------| | Compilazione degli shader | A runtime, causa scatti al primo avvio | In anticipo, durante il build | | Backend iOS | OpenGL / Metal | Metal | | Backend Android | OpenGL | Vulkan, ripiego su OpenGL | | Fluidità della prima animazione | Scatti occasionali | Costante | Per la maggior parte dei progetti la migrazione è gratuita: le versioni recenti di Flutter attivano Impeller automaticamente, e i vecchi espedienti di preriscaldamento degli shader (`--purge-persistent-cache`, file `.sksl` inclusi) non sono più necessari e possono essere eliminati. Anche gli shader `FragmentProgram` personalizzati scritti con il sottoinsieme di GLSL supportato da Flutter continuano a funzionare, poiché Impeller li compila attraverso la stessa pipeline offline degli effetti nativi del framework. Verificare quale motore esegue un'app richiede una riga: una build di debug stampa il backend attivo all'avvio, e l'inspector di DevTools lo segnala nei dettagli di rendering dell'app. Una volta confermato Impeller, il resto del lavoro sulle prestazioni sale verso il livello dei widget, dove il framework ricostruisce molto più spesso di quanto la schermata cambi davvero. È lì che si concentra il resto di questa guida, perché un motore veloce non può salvare un albero di widget che si ridisegna centinaia di volte al secondo. ## Ridurre le ricostruzioni di Flutter con const e la suddivisione dei widget Ogni `setState` marca il suo widget come non aggiornato e riesegue `build` per quell'elemento e il suo sottoalbero. Il trucco è mantenere quel sottoalbero il più stretto possibile. Due abitudini svolgono gran parte del lavoro: marcare i widget statici come `const` e spingere lo stato verso il basso, così che la parte con stato resti piccola. Un widget `const` viene canonicalizzato in un'unica istanza, così che quando un genitore si ricostruisce Flutter confronta la referenza identica, non rileva alcun cambiamento e salta del tutto il ridisegno di quel ramo. ```dart // product_screen.dart // A cart update repaints the badge, not the header. class ProductScreen extends StatefulWidget { const ProductScreen({super.key}); @override State createState() => _ProductScreenState(); } class _ProductScreenState extends State { int _cartCount = 0; @override Widget build(BuildContext context) { return Column( children: [ const ProductHeader(), // const: never rebuilds on setState CartBadge(count: _cartCount), // only this subtree rebuilds FilledButton( onPressed: () => setState(() => _cartCount++), child: const Text('Add to cart'), ), ], ); } } ``` Abilitare il lint `prefer_const_constructors` in `analysis_options.yaml` trasforma questa disciplina in un controllo del compilatore, così che le opportunità di `const` non regrediscano mai in silenzio. La regola correlata `prefer_const_literals_to_create_immutables` estende la stessa garanzia a liste e mappe passate ai widget. Anche le chiavi dei widget meritano una menzione. Quando una lista viene riordinata o un widget mantiene lo stesso tipo ma cambia identità, una `ValueKey` o una `ObjectKey` consente a Flutter di associare il vecchio elemento alla giusta nuova istanza di widget invece di smontare e ricostruire lo stato. L'assenza di chiavi è una causa comune di perdita della posizione di scorrimento e di reset delle animazioni nelle liste dinamiche, e il lavoro di ricostruzione aggiuntivo che comporta è facile da trascurare. ## Circoscrivere le ricostruzioni ai dati che sono cambiati Quando lo stato vive in alto nell'albero, anche un layout ben suddiviso si ricostruisce parecchio. La soluzione è trasmettere un valore e lasciare che un solo widget lo ascolti. Flutter fornisce `ValueNotifier` e `ValueListenableBuilder` esattamente per questo, senza bisogno di alcun pacchetto esterno. ```dart // article_list.dart // A scroll-to-top button that rebuilds alone, not the whole page. class ArticleList extends StatefulWidget { const ArticleList({super.key}); @override State createState() => _ArticleListState(); } class _ArticleListState extends State { final _showFab = ValueNotifier(false); final _controller = ScrollController(); @override void initState() { super.initState(); // Update the notifier, not setState: the list never rebuilds _controller.addListener(() => _showFab.value = _controller.offset > 400); } @override void dispose() { _showFab.dispose(); _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: ListView.builder( controller: _controller, itemCount: 200, itemBuilder: (_, i) => ListTile(title: Text('Item $i')), ), floatingActionButton: ValueListenableBuilder( valueListenable: _showFab, builder: (_, visible, __) => visible ? FloatingActionButton( onPressed: () => _controller.jumpTo(0), child: const Icon(Icons.arrow_upward), ) : const SizedBox.shrink(), ), ); } } ``` Le librerie di gestione dello stato generalizzano la stessa idea. Il `ref.watch(provider.select(...))` di Riverpod e il widget `Selector` di Provider si ricostruiscono entrambi solo quando cambia una porzione scelta dello stato, il che fa la differenza tra fluido e a scatti nelle schermate ricche di dati. I compromessi tra queste librerie sono trattati nella guida sulla [gestione dello stato in Flutter nel 2026](/blog/flutter/flutter-state-management-2026-riverpod-bloc-getx). > **Attenzione all'ambito della ricostruzione** > > Chiamare `setState` vicino alla radice di una schermata, o avvolgere un'intera pagina in un `Consumer` di primo livello, ridisegna in silenzio centinaia di widget a ogni cambiamento. Fai il profiling con il contatore delle ricostruzioni di DevTools prima di dare per scontato che una schermata sia economica. ## Isolare i ridisegni costosi con RepaintBoundary Ricostruire e ridisegnare sono fasi distinte. Un widget può saltare la ricostruzione e ridisegnarsi comunque perché un vicino rumoroso condivide il suo layer. `RepaintBoundary` assegna a un sottoalbero un layer proprio, così che un grafico animato non possa costringere schede statiche a ridisegnarsi a ogni frame. ```dart // dashboard.dart // The live chart repaints on its own layer, cards stay untouched. class Dashboard extends StatelessWidget { const Dashboard({required this.ticker, super.key}); final Stream ticker; @override Widget build(BuildContext context) { return Column( children: [ // SummaryCards is static, so isolate it from the animation below const RepaintBoundary(child: SummaryCards()), RepaintBoundary( child: StreamBuilder( stream: ticker, builder: (_, snap) => LivePriceChart(value: snap.data ?? 0), ), ), ], ); } } ``` RepaintBoundary non è gratuito: ognuno alloca un layer, quindi disseminarlo ovunque danneggia più di quanto aiuti. Conviene riservarlo alle regioni realmente animate e alle lunghe liste scorrevoli, confermando il guadagno nella timeline. Il [riferimento all'API RepaintBoundary](https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html) documenta come l'overlay di debug del ridisegno evidenzi quali layer vengono effettivamente ridisegnati. ## Mantenere il metodo build leggero e privo di effetti collaterali Un metodo `build` viene eseguito a ogni ricostruzione, quindi tutto ciò che è costoso al suo interno si moltiplica. La regola è che `build` deve leggere lo stato e restituire widget, niente di più. Allocare un `ScrollController`, un `AnimationController` o un `Future` dentro `build` lo ricrea a ogni frame e disperde l'istanza precedente. Quegli oggetti appartengono a `initState` o a un campo `late final`, rilasciato in `dispose`. Il lavoro sincrono pesante è la seconda trappola. Fare il parsing di JSON, filtrare una lista grande o formattare date dentro `build` riesegue quel lavoro a ogni ridisegno, anche quando i dati sottostanti non sono cambiati. Calcola il risultato una volta quando l'input cambia e mettilo in cache, oppure spostalo in un `FutureBuilder` così che il framework non blocchi un frame in attesa. Per i valori derivati da altri valori, memoizzali dietro un getter che ricalcola solo quando cambiano le sue dipendenze. Il widget `Opacity` è un colpevole specifico che vale la pena nominare. Avvolgere un sottoalbero in `Opacity` forza un `saveLayer` fuori schermo, una delle operazioni più costose della pipeline. Per un'animazione di dissolvenza, `AnimatedOpacity` o una `FadeTransition` sono più economiche, e per una tinta fissa applicare il colore a livello di paint tramite una `foregroundDecoration` o uno shader evita del tutto il layer. La stessa cautela vale per `ClipPath` e `ClipRRect` con antialiasing su superfici grandi, che dietro le quinte innescano anch'essi un `saveLayer`. Infine, preferisci le collezioni `const` e le callback che non catturano nuove closure a ogni build. Un `const []` passato come valore predefinito e un riferimento a un metodo invece di una lambda inline mantengono economici i controlli di uguaglianza dei widget, il che consente al cortocircuito di `const` descritto prima di scattare davvero. ## Costruire liste lunghe e immagini in modo efficiente I feed lunghi e le griglie di immagini sono il punto in cui il codice Flutter ingenuo perde frame. Due regole coprono la maggior parte dei casi: costruire le righe in modo lazy e decodificare le immagini alla dimensione con cui vengono mostrate invece che alla loro risoluzione di origine. ```dart // feed.dart // Lazy rows plus right-sized image decoding keep scrolling at 60fps. class Feed extends StatelessWidget { const Feed({required this.posts, super.key}); final List posts; @override Widget build(BuildContext context) { return ListView.builder( itemCount: posts.length, // build on demand, not all at once cacheExtent: 600, // pre-build past the viewport edge itemBuilder: (context, index) { final post = posts[index]; return ListTile( leading: Image.network( post.thumbnailUrl, width: 48, height: 48, cacheWidth: 96, // decode at display size, save memory ), title: Text(post.title), ); }, ); } } ``` Un'immagine a piena risoluzione da 3000 pixel compressa in un avatar da 48 pixel spreca memoria e tempo di decodifica per ognuna di esse. Impostare `cacheWidth` dice al motore di decodificare alla dimensione target, il che da solo può ridurre l'impronta di memoria di una lista di un ordine di grandezza. Le [buone pratiche sulle prestazioni](https://docs.flutter.dev/perf/best-practices) di Flutter raccolgono ulteriori indicazioni su `Opacity`, `saveLayer` e l'uso dei clip che seguono lo stesso principio. ## Misurare l'ottimizzazione di Flutter nel 2026 con DevTools Ogni regola sopra va verificata, non presunta. Flutter DevTools è la fonte di verità. L'overlay delle prestazioni (`P` nella console di debug, o `flutter run --profile`) disegna due grafici, UI e raster, e ogni barra che supera la linea verde è un frame perso. La vista timeline attribuisce poi ogni frame lento a una specifica chiamata di `build`, layout o paint. Fai sempre il profiling in modalità `--profile` su un dispositivo fisico. Le build di debug eseguono Dart non ottimizzato e gonfiano ogni misurazione, così che una schermata che scatta in debug spesso gira alla perfezione una volta compilata. La [guida alle prestazioni di DevTools](https://docs.flutter.dev/tools/devtools/performance) illustra come leggere il grafico dei frame e individuare le fonti di scatto, e lo stesso riflesso di profiling ritorna nei colloqui sulle [animazioni e il rendering in Flutter](/technologies/flutter/interview-questions/animations). > **Un rapido ciclo di profiling** > > Avvia in modalità profile, riproduci l'interazione a scatti, apri la timeline e ordina i frame per durata. Il frame peggiore punta quasi sempre a un'unica ricostruzione sovradimensionata o a un'immagine non messa in cache, il che è molto più veloce che tirare a indovinare. L'[hub tecnologico di Flutter](/technologies/flutter) collega gli argomenti di gestione dello stato, testing e rendering che completano una strategia di prestazioni in produzione. ## Conclusione Le prestazioni di Flutter nel 2026 hanno meno a che fare con i micro-trucchi e più con una breve lista di abitudini applicate con costanza: - Distribuire su Impeller ed eliminare il codice legacy di preriscaldamento degli shader; il problema dello scatto al primo frame è risolto a livello di motore. - Marcare i widget statici come `const` e imporlo con il lint `prefer_const_constructors`. - Suddividere i widget con stato in unità piccole, così che ogni `setState` ridisegni il sottoalbero più stretto possibile. - Trasmettere i valori che cambiano tramite `ValueListenableBuilder` o un'API di tipo `select` invece di ricostruire intere schermate. - Avvolgere le regioni realmente animate in un `RepaintBoundary`, ma misurare prima di aggiungerne altri. - Costruire le liste con `ListView.builder` e decodificare le immagini alla dimensione di visualizzazione usando `cacheWidth`. - Fare il profiling in modalità `--profile` su hardware reale e lasciare che sia DevTools, non l'intuito, a decidere cosa ottimizzare in seguito. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/it/blog/flutter/flutter-performance-optimization-2026-impeller-rebuilds