# Оптимізація продуктивності Flutter у 2026: Impeller, перебудови та найкращі практики > Як утримувати застосунки Flutter на стабільних 60 або 120 fps у 2026 за допомогою Impeller, дисциплінованих перебудов віджетів, RepaintBoundary та профілювання в DevTools. - Published: 2026-07-04 - Updated: 2026-07-07 - Author: SharpSkill - Tags: flutter, performance, impeller, best-practices, dart, mobile - Reading time: 9 min --- Оптимізація продуктивності Flutter у 2026 році спирається на дві зміни, що переінакшили спосіб побудови плавних застосунків: рушій рендерингу Impeller тепер увімкнено за замовчуванням на iOS та Android, а модель перебудови фреймворку винагороджує розробників, які тримають дерева віджетів малими й незмінними. Команди, що спираються на обидва важелі, утримують стабільні 60 або 120 fps без ручного налаштування кожного кадру. > **Зміна з найбільшим впливом** > > Найбільший виграш у більшості застосунків Flutter полягає в додаванні конструкторів `const` і поділі великих методів `build`, щоб виклик `setState` перемальовував одну кнопку замість цілого екрана. ## Чому Impeller наново задає базову лінію продуктивності Flutter Impeller — це рушій рендерингу Flutter, і він замінив Skia як стандартний на мобільних платформах. Різниця найбільше важить у перші секунди анімації. Skia компілювала свої шейдери на льоту, коли певний ефект уперше з’являвся на екрані, що спричиняло сумнозвісне підгальмовування при першому запуску, за яке критикували застосунки Flutter. Impeller компілює ці шейдери завчасно, під час збірки, тож найперший кадр анімації такий самий плавний, як і сотий. Impeller також націлюється безпосередньо на сучасні графічні API: Metal на iOS і Vulkan на Android, з відкатом до OpenGL для старішого обладнання Android. [Документація рушія Impeller на GitHub](https://github.com/flutter/flutter/blob/master/docs/engine/impeller/README.md) докладно описує архітектуру рендерингу та матрицю підтримки платформ. | Аспект | Skia (успадкована) | Impeller (типовий 2026) | |--------|---------------|-------------------------| | Компіляція шейдерів | Під час виконання, спричиняє підгальмовування при першому запуску | Завчасно, під час збірки | | Бекенд iOS | OpenGL / Metal | Metal | | Бекенд Android | OpenGL | Vulkan, відкат до OpenGL | | Плавність першої анімації | Час від часу підгальмовує | Стабільна | Для більшості проєктів міграція безкоштовна: нові випуски Flutter вмикають Impeller автоматично, а старі обхідні прийоми прогріву шейдерів (`--purge-persistent-cache`, вбудовані файли `.sksl`) більше не потрібні й можуть бути видалені. Власні шейдери `FragmentProgram`, написані під підмножину GLSL, яку підтримує Flutter, також продовжують працювати, оскільки Impeller компілює їх тим самим офлайн-конвеєром, що й власні ефекти фреймворку. Перевірити, на якому рушії працює застосунок, — це один рядок: збірка debug виводить активний бекенд під час запуску, а інспектор DevTools повідомляє його в деталях рендерингу застосунку. Щойно Impeller підтверджено, решта роботи над продуктивністю піднімається до шару віджетів, де фреймворк перебудовує значно частіше, ніж екран насправді змінюється. Саме на цьому зосереджується решта цього посібника, адже швидкий рушій не врятує дерево віджетів, що перемальовує себе сотні разів на секунду. ## Скорочення перебудов Flutter за допомогою const і поділу віджетів Кожен `setState` позначає свій віджет як застарілий і повторно запускає `build` для цього елемента та його піддерева. Хитрість у тому, щоб тримати це піддерево якомога вужчим. Дві звички роблять більшу частину роботи: позначати статичні віджети як `const` і спускати стан униз, щоб частина зі станом залишалася малою. Віджет `const` канонікалізується до єдиного екземпляра, тож коли батько перебудовується, Flutter порівнює ідентичне посилання, не бачить змін і повністю пропускає перемалювання цієї гілки. ```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'), ), ], ); } } ``` Увімкнення лінта `prefer_const_constructors` у `analysis_options.yaml` перетворює цю дисципліну на перевірку компілятора, тож можливості для `const` ніколи не зникають непомітно. Пов’язане правило `prefer_const_literals_to_create_immutables` поширює ту саму гарантію на списки та мапи, що передаються у віджети. Ключі віджетів теж заслуговують на згадку. Коли список змінює порядок або віджет зберігає той самий тип, але змінює ідентичність, `ValueKey` чи `ObjectKey` дає Flutter змогу зіставити старий елемент із правильним новим екземпляром віджета замість того, щоб зруйнувати й відтворити стан. Відсутність ключів — поширена причина втрати позиції прокручування та скидання анімацій у динамічних списках, а спричинену ними додаткову роботу з перебудови легко не помітити. ## Обмеження перебудов даними, що змінилися Коли стан живе високо в дереві, навіть добре поділений макет усе одно багато перебудовується. Розв’язання — транслювати значення й дозволити одному віджету його слухати. Flutter постачає `ValueNotifier` та `ValueListenableBuilder` саме для цього, без жодного зовнішнього пакета. ```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(), ), ); } } ``` Бібліотеки керування станом узагальнюють ту саму ідею. `ref.watch(provider.select(...))` у Riverpod і віджет `Selector` у Provider перебудовуються лише тоді, коли змінюється обраний зріз стану, що й вирішує різницю між плавним і смиканим на екранах, насичених даними. Компроміси між цими бібліотеками розглянуто в посібнику з [керування станом у Flutter у 2026](/blog/flutter/flutter-state-management-2026-riverpod-bloc-getx). > **Стежте за обсягом перебудови** > > Виклик `setState` поблизу кореня екрана або обгортання цілої сторінки у `Consumer` верхнього рівня тихо перемальовує сотні віджетів на кожну зміну. Профілюйте лічильником перебудов у DevTools, перш ніж припускати, що екран дешевий. ## Ізоляція дорогих перемалювань за допомогою RepaintBoundary Перебудова й перемалювання — це окремі етапи. Віджет може пропустити перебудову й усе одно перемалюватися, бо галасливий сусід ділить його шар. `RepaintBoundary` надає піддереву власний шар, тож анімований графік не може змусити статичні картки перемальовуватися щокадру. ```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 не безкоштовний: кожен виділяє шар, тож розсипати його всюди шкодить більше, ніж допомагає. Варто застосовувати його навколо справді анімованих ділянок і довгих прокручуваних списків, підтверджуючи виграш на часовій шкалі. [Довідник API RepaintBoundary](https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html) документує, як налагоджувальний оверлей перемалювання підсвічує, які шари насправді перемальовуються. ## Тримати метод build дешевим і без побічних ефектів Метод `build` виконується під час кожної перебудови, тож усе дороге всередині нього множиться. Правило таке: `build` має читати стан і повертати віджети, не більше. Виділення `ScrollController`, `AnimationController` чи `Future` всередині `build` відтворює їх щокадру й губить попередній екземпляр. Ці об’єкти належать до `initState` або до поля `late final`, що звільняється в `dispose`. Важка синхронна робота — друга пастка. Розбір JSON, фільтрування великого списку чи форматування дат усередині `build` повторно виконують цю роботу під час кожного перемалювання, навіть коли базові дані не змінилися. Обчисліть результат один раз, коли змінюється вхід, і кешуйте його, або перенесіть його в `FutureBuilder`, щоб фреймворк не блокував кадр в очікуванні. Для значень, похідних від інших значень, мемоїзуйте їх за гетером, який перераховує лише тоді, коли змінюються його залежності. Віджет `Opacity` — конкретний винуватець, що вартий згадки. Обгортання піддерева в `Opacity` примусово запускає позаекранний `saveLayer` — одну з найдорожчих операцій конвеєра. Для анімації згасання `AnimatedOpacity` або `FadeTransition` дешевші, а для фіксованого відтінку застосування кольору на рівні малювання через `foregroundDecoration` чи шейдер узагалі уникає шару. Та сама обережність стосується `ClipPath` і `ClipRRect` зі згладжуванням на великих поверхнях, які теж запускають `saveLayer` за лаштунками. Насамкінець надавайте перевагу колекціям `const` і колбекам, що не захоплюють свіжі замикання під час кожної збірки. Передане як типове `const []` і посилання на метод замість вбудованої лямбди тримають перевірки рівності віджетів дешевими, що дає змогу описаному раніше короткому замиканню `const` справді спрацювати. ## Ефективна побудова довгих списків і зображень Довгі стрічки й сітки зображень — це те місце, де наївний код Flutter губить кадри. Два правила покривають більшість випадків: будувати рядки ліниво й декодувати зображення в тому розмірі, в якому вони відображаються, а не в роздільній здатності джерела. ```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), ); }, ); } } ``` Зображення повної роздільної здатності на 3000 пікселів, стиснуте в аватар на 48 пікселів, марнує пам’ять і час декодування на кожному з них. Установлення `cacheWidth` каже рушію декодувати в цільовому розмірі, що саме собою може зменшити споживання пам’яті списком на порядок. [Найкращі практики продуктивності](https://docs.flutter.dev/perf/best-practices) Flutter збирають додаткові поради щодо `Opacity`, `saveLayer` та використання обрізань, які дотримуються того самого принципу. ## Вимірювання оптимізації Flutter у 2026 за допомогою DevTools Кожне правило вище слід перевіряти, а не припускати. Flutter DevTools — джерело істини. Оверлей продуктивності (`P` у консолі debug або `flutter run --profile`) малює два графіки, UI та растр, і будь-яка смуга, що перетинає зелену лінію, — це втрачений кадр. Подання часової шкали далі приписує кожен повільний кадр конкретному виклику `build`, компонування чи малювання. Завжди профілюйте в режимі `--profile` на фізичному пристрої. Збірки debug виконують неоптимізований Dart і роздувають кожен вимір, тож екран, що смикається в debug, часто працює бездоганно після компіляції. [Посібник із продуктивності DevTools](https://docs.flutter.dev/tools/devtools/performance) проводить через читання графіка кадрів і виявлення джерел смикання, і той самий навик профілювання зринає на співбесідах про [анімації та рендеринг у Flutter](/technologies/flutter/interview-questions/animations). > **Швидкий цикл профілювання** > > Запустіть у режимі profile, відтворіть смикану взаємодію, відкрийте часову шкалу й відсортуйте кадри за тривалістю. Найгірший кадр майже завжди вказує на єдину завелику перебудову чи некешоване зображення, що набагато швидше, ніж вгадувати. [Технологічний хаб Flutter](/technologies/flutter) пов’язує теми керування станом, тестування та рендерингу, що доповнюють продуктивну стратегію для продакшену. ## Висновок Продуктивність Flutter у 2026 — це менше про мікрохитрощі й більше про короткий перелік звичок, застосованих послідовно: - Постачайте на Impeller і видаляйте застарілий код прогріву шейдерів; проблему смикання першого кадру розв’язано на рівні рушія. - Позначайте статичні віджети як `const` і забезпечуйте це лінтом `prefer_const_constructors`. - Дробіть віджети зі станом на малі одиниці, щоб кожен `setState` перемальовував найвужче можливе піддерево. - Транслюйте змінні значення через `ValueListenableBuilder` чи API у стилі `select` замість перебудови цілих екранів. - Обгортайте справді анімовані ділянки в `RepaintBoundary`, але вимірюйте, перш ніж додавати ще. - Будуйте списки з `ListView.builder` і декодуйте зображення в розмірі відображення за допомогою `cacheWidth`. - Профілюйте в режимі `--profile` на реальному обладнанні й дозволяйте DevTools, а не інтуїції, вирішувати, що оптимізувати далі. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/flutter/flutter-performance-optimization-2026-impeller-rebuilds