# Optimización del rendimiento en Flutter en 2026: Impeller, reconstrucciones y buenas prácticas > Cómo mantener aplicaciones Flutter a 60 o 120 fps constantes en 2026 con Impeller, reconstrucciones de widgets disciplinadas, RepaintBoundary y perfilado con DevTools. - Published: 2026-07-04 - Updated: 2026-07-07 - Author: SharpSkill - Tags: flutter, performance, impeller, best-practices, dart, mobile - Reading time: 9 min --- La optimización del rendimiento en Flutter en 2026 se apoya en dos cambios que transformaron la forma de construir aplicaciones fluidas: el motor de renderizado Impeller ahora viene activado de forma predeterminada en iOS y Android, y el modelo de reconstrucción del framework recompensa a quienes mantienen árboles de widgets pequeños e inmutables. Los equipos que aprovechan ambas palancas sostienen 60 o 120 fps constantes sin ajustar cada frame a mano. > **El cambio de mayor impacto** > > La mayor ganancia en la mayoría de las aplicaciones Flutter consiste en agregar constructores `const` y dividir los métodos `build` grandes, para que una llamada a `setState` repinte un solo botón en lugar de una pantalla completa. ## Por qué Impeller redefine la referencia de rendimiento de Flutter Impeller es el motor de renderizado de Flutter y reemplazó a Skia como opción predeterminada en las plataformas móviles. La diferencia importa sobre todo en los primeros segundos de una animación. Skia compilaba sus shaders sobre la marcha, la primera vez que un efecto aparecía en pantalla, lo que producía el famoso tirón del primer arranque por el que se criticaba a las aplicaciones Flutter. Impeller compila esos shaders con antelación, durante la fase de build, de modo que el primer frame de una animación es tan fluido como el número cien. Impeller también apunta directamente a las API gráficas modernas: Metal en iOS y Vulkan en Android, con un respaldo en OpenGL para hardware Android más antiguo. La [documentación del motor Impeller en GitHub](https://github.com/flutter/flutter/blob/master/docs/engine/impeller/README.md) detalla la arquitectura de renderizado y la matriz de compatibilidad de plataformas. | Aspecto | Skia (heredado) | Impeller (predeterminado 2026) | |--------|---------------|-------------------------| | Compilación de shaders | En tiempo de ejecución, causa tirones en el primer arranque | Con antelación, en el build | | Backend de iOS | OpenGL / Metal | Metal | | Backend de Android | OpenGL | Vulkan, respaldo OpenGL | | Fluidez de la primera animación | Tirones ocasionales | Constante | Para la mayoría de los proyectos la migración es gratuita: las versiones recientes de Flutter activan Impeller automáticamente, y los antiguos rodeos de precalentamiento de shaders (`--purge-persistent-cache`, archivos `.sksl` empaquetados) ya no son necesarios y pueden eliminarse. Los shaders `FragmentProgram` personalizados escritos con el subconjunto de GLSL que admite Flutter también se conservan, ya que Impeller los compila a través del mismo pipeline sin conexión que los efectos nativos del framework. Verificar qué motor ejecuta una aplicación toma una sola línea: un build de depuración imprime el backend activo al arrancar, y el inspector de DevTools lo indica en los detalles de renderizado de la aplicación. Una vez confirmado Impeller, el resto del trabajo de rendimiento sube hacia la capa de widgets, donde el framework reconstruye mucho más a menudo de lo que la pantalla cambia en realidad. Ahí se centra el resto de esta guía, porque un motor rápido no puede salvar un árbol de widgets que se repinta cientos de veces por segundo. ## Reducir las reconstrucciones de Flutter con const y la división de widgets Cada `setState` marca su widget como obsoleto y vuelve a ejecutar `build` para ese elemento y su subárbol. El truco está en mantener ese subárbol lo más estrecho posible. Dos hábitos hacen la mayor parte del trabajo: marcar los widgets estáticos como `const` y empujar el estado hacia abajo para que la parte con estado sea pequeña. Un widget `const` se canonicaliza en una sola instancia, así que cuando un padre se reconstruye, Flutter compara la referencia idéntica, no detecta cambios y omite por completo el repintado de esa rama. ```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'), ), ], ); } } ``` Activar el lint `prefer_const_constructors` en `analysis_options.yaml` convierte esta disciplina en una comprobación del compilador, de modo que las oportunidades de `const` nunca retroceden en silencio. La regla relacionada `prefer_const_literals_to_create_immutables` extiende la misma garantía a las listas y los mapas que se pasan a los widgets. Las claves de widget también merecen una mención. Cuando una lista se reordena o un widget conserva el mismo tipo pero cambia de identidad, una `ValueKey` o una `ObjectKey` permite que Flutter asocie el elemento antiguo con la instancia de widget nueva correcta en lugar de destruir y reconstruir el estado. La ausencia de claves es una causa frecuente de pérdida de posición de desplazamiento y de reinicio de animaciones en listas dinámicas, y el trabajo de reconstrucción adicional que provoca pasa fácilmente desapercibido. ## Acotar las reconstrucciones a los datos que cambiaron Cuando el estado vive en la parte alta del árbol, incluso un diseño bien dividido se reconstruye mucho. La solución consiste en difundir un valor y dejar que un solo widget lo escuche. Flutter incluye `ValueNotifier` y `ValueListenableBuilder` precisamente para esto, sin necesidad de ningún paquete externo. ```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(), ), ); } } ``` Las bibliotecas de gestión de estado generalizan la misma idea. El `ref.watch(provider.select(...))` de Riverpod y el widget `Selector` de Provider solo se reconstruyen cuando cambia una porción elegida del estado, lo que marca la diferencia entre fluido y con tirones en pantallas cargadas de datos. Las concesiones entre esas bibliotecas se tratan en la guía sobre [gestión de estado en Flutter en 2026](/blog/flutter/flutter-state-management-2026-riverpod-bloc-getx). > **Cuidado con el alcance de la reconstrucción** > > Llamar a `setState` cerca de la raíz de una pantalla, o envolver una página entera en un `Consumer` de alto nivel, repinta en silencio cientos de widgets en cada cambio. Perfila con el contador de reconstrucciones de DevTools antes de suponer que una pantalla es barata. ## Aislar los repintados costosos con RepaintBoundary Reconstruir y repintar son etapas separadas. Un widget puede omitir la reconstrucción y aun así repintarse porque un vecino inquieto comparte su capa. `RepaintBoundary` le da a un subárbol su propia capa, de modo que un gráfico animado no puede forzar a tarjetas estáticas a redibujarse en cada 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 no es gratis: cada uno asigna una capa, así que esparcirlo por todas partes perjudica más de lo que ayuda. Conviene reservarlo para regiones realmente animadas y listas largas con desplazamiento, y confirmar la mejora en la línea de tiempo. La [referencia de la API RepaintBoundary](https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html) documenta cómo la superposición de depuración de repintado resalta qué capas se redibujan realmente. ## Mantener el método build barato y sin efectos secundarios Un método `build` se ejecuta en cada reconstrucción, así que todo lo costoso que haya dentro se multiplica. La regla es que `build` debe leer el estado y devolver widgets, nada más. Asignar un `ScrollController`, un `AnimationController` o un `Future` dentro de `build` lo recrea en cada frame y filtra la instancia anterior. Esos objetos pertenecen a `initState` o a un campo `late final`, liberado en `dispose`. El trabajo síncrono pesado es la segunda trampa. Analizar JSON, filtrar una lista grande o formatear fechas dentro de `build` vuelve a ejecutar ese trabajo en cada repintado, incluso cuando los datos subyacentes no han cambiado. Calcula el resultado una vez cuando cambia la entrada y guárdalo en caché, o muévelo a un `FutureBuilder` para que el framework no bloquee un frame esperándolo. Para valores derivados de otros valores, memorízalos detrás de un getter que solo recalcule cuando cambian sus dependencias. El widget `Opacity` es un culpable concreto que vale la pena nombrar. Envolver un subárbol en `Opacity` fuerza un `saveLayer` fuera de pantalla, una de las operaciones más costosas del pipeline. Para una animación de desvanecimiento, `AnimatedOpacity` o una `FadeTransition` resultan más baratas, y para un tinte fijo, aplicar el color en el nivel de pintado mediante un `foregroundDecoration` o un shader evita la capa por completo. La misma precaución se aplica a `ClipPath` y `ClipRRect` con suavizado en superficies grandes, que también activan un `saveLayer` de forma interna. Por último, prefiere las colecciones `const` y los callbacks que no capturan closures nuevas en cada build. Un `const []` pasado como valor predeterminado y una referencia a un método en lugar de una lambda en línea mantienen baratas las comprobaciones de igualdad de widgets, lo que permite que el cortocircuito de `const` descrito antes se dispare de verdad. ## Construir listas largas e imágenes de forma eficiente Los feeds largos y las cuadrículas de imágenes son donde el código Flutter ingenuo pierde frames. Dos reglas cubren la mayoría de los casos: construir las filas de forma perezosa y decodificar las imágenes al tamaño en que se muestran en lugar de a su resolución de origen. ```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), ); }, ); } } ``` Una imagen a resolución completa de 3000 píxeles comprimida en un avatar de 48 píxeles desperdicia memoria y tiempo de decodificación en cada una de ellas. Definir `cacheWidth` le indica al motor que decodifique al tamaño objetivo, lo que por sí solo puede reducir la huella de memoria de una lista en un orden de magnitud. Las [buenas prácticas de rendimiento](https://docs.flutter.dev/perf/best-practices) de Flutter reúnen más consejos sobre `Opacity`, `saveLayer` y el uso de recortes que siguen el mismo principio. ## Medir la optimización de Flutter en 2026 con DevTools Cada regla anterior debe verificarse, no suponerse. Flutter DevTools es la fuente de verdad. La superposición de rendimiento (`P` en la consola de depuración, o `flutter run --profile`) dibuja dos gráficas, UI y raster, y cualquier barra que cruce la línea verde es un frame perdido. La vista de línea de tiempo atribuye luego cada frame lento a una llamada concreta de `build`, layout o pintado. Perfila siempre en modo `--profile` en un dispositivo físico. Los builds de depuración ejecutan Dart sin optimizar e inflan cada medición, así que una pantalla que da tirones en depuración a menudo corre a la perfección una vez compilada. La [guía de rendimiento de DevTools](https://docs.flutter.dev/tools/devtools/performance) explica cómo leer el gráfico de frames y detectar las fuentes de tirones, y ese mismo músculo de perfilado aparece en las entrevistas sobre [animaciones y renderizado en Flutter](/technologies/flutter/interview-questions/animations). > **Un ciclo de perfilado rápido** > > Ejecuta en modo profile, reproduce la interacción con tirones, abre la línea de tiempo y ordena los frames por duración. El peor frame casi siempre apunta a una única reconstrucción sobredimensionada o a una imagen sin caché, lo que es mucho más rápido que adivinar. El [hub tecnológico de Flutter](/technologies/flutter) enlaza los temas de gestión de estado, pruebas y renderizado que completan una estrategia de rendimiento en producción. ## Conclusión El rendimiento en Flutter en 2026 tiene menos que ver con microtrucos y más con una lista corta de hábitos aplicados con constancia: - Publicar sobre Impeller y eliminar el código heredado de precalentamiento de shaders; el problema de tirones en el primer frame está resuelto a nivel del motor. - Marcar los widgets estáticos como `const` e imponerlo con el lint `prefer_const_constructors`. - Dividir los widgets con estado en unidades pequeñas, para que cada `setState` repinte el subárbol más estrecho posible. - Difundir los valores cambiantes mediante `ValueListenableBuilder` o una API de tipo `select` en lugar de reconstruir pantallas enteras. - Envolver las regiones realmente animadas en un `RepaintBoundary`, pero medir antes de agregar más. - Construir las listas con `ListView.builder` y decodificar las imágenes al tamaño de visualización usando `cacheWidth`. - Perfilar en modo `--profile` en hardware real y dejar que DevTools, no la intuición, decida qué optimizar a continuación. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/es/blog/flutter/flutter-performance-optimization-2026-impeller-rebuilds