Widget — що це, види та композиція у Flutter

Автор: IT Sectr Опубліковано: 2026-06-30 Час читання: 10 хв

Widget — це центральне поняття фреймворку Flutter, що описує конфігурацію елемента інтерфейсу користувача. Кожен візуальний компонент, від кнопки до складної анімації, є Widget. На відміну від інших фреймворків, де UI описується окремими XML-файлами або малюється імперативно, Flutter будує інтерфейс через композицію Widget — об'єднання маленьких неподільних елементів в ієрархічне дерево. За даними Flutter Documentation (2025), бібліотека Flutter SDK включає понад 260 вбудованих Widget, що покривають Material Design, Cupertino та кастомні стилі.

Головне

  • Widget — базовий будівельний блок UI у Flutter, що описує конфігурацію елемента.
  • Композиція — UI будується через вкладення Widget один в одного, а не через успадкування.
  • StatelessWidget — віджет, який не змінюється після відтворення (текст, іконка, відступ).
  • StatefulWidget — віджет із змінним станом (форми, анімації, списки з даними).
  • Element tree — Flutter підтримує три дерева: Widget, Element та RenderObject.

Що таке Widget у Flutter

Widget у Flutter — це незмінний (immutable) опис частини інтерфейсу користувача. Кожен Widget містить конфігураційні властивості: розмір, колір, положення, текст, обробники подій та дочірні Widget. Самі Widget не відтворюються безпосередньо — вони є рецептами (blueprints), на основі яких Flutter Engine створює RenderObject — реальний графічний об'єкт на екрані.

Філософія Flutter говорить: «Everything is a Widget». Це означає, що не лише видимі елементи (Text, Image, Button) є Widget, але й структурні блоки (Padding, Center, Column, Stack), поведінкові блоки (GestureDetector, AnimatedBuilder) і навіть сам застосунок (MaterialApp, CupertinoApp). Такий підхід забезпечує єдність: будь-який елемент екрана можна комбінувати з будь-яким іншим через просте вкладення.

За даними Google I/O 2024 — Flutter Widgets Deep Dive, середній застосунок на Flutter містить від 200 до 1500 Widget у будь-який момент часу. Незважаючи на таку кількість, Flutter підтримує 60 FPS навіть на бюджетних пристроях завдяки оптимізаціям на рівні C++ Skia/Impeller engine. Widget — це легкі об'єкти (40–80 байт кожен), тому їх створення не є вузьким місцем продуктивності.

dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text('Привіт, Flutter!'),
        ),
      ),
    );
  }
}

Три дерева Flutter: Widget, Element та RenderObject

Щоб зрозуміти, як працює Widget, потрібно розібратися з архітектурою Flutter, що складається з трьох взаємопов'язаних дерев. Перше — Widget tree — описує конфігурацію UI. Це легке дерево, яке може повністю перебудовуватися при кожному кадрі (збирач сміття видаляє старі Widget і створює нові). Widget незмінні (immutable): якщо змінився колір тексту — створюється новий Widget Text з новим кольором, старий відкидається.

Друге дерево — Element tree — сполучна ланка між Widget та RenderObject. Element містить посилання на Widget (конфігурацію) та на RenderObject (відтворення). Коли Widget змінюється, Flutter порівнює новий Widget зі старим Element і вирішує: оновити існуючий RenderObject (якщо Widget того ж типу) або створити новий (якщо тип Widget змінився). Цей процес називається Reconciliation і є аналогом React Virtual DOM.

Третє дерево — RenderObject tree — відповідає за реальне відтворення на екрані. RenderObject містить конкретні розміри, позиції та методи paint. Flutter Engine (C++ Skia або Impeller) проходить по RenderObject tree і відтворює кожен вузол. RenderObject tree — найважче дерево, тому Flutter мінімізує його зміни, перевикористовуючи RenderObject при зміні Widget одного типу.

ДеревоПризначенняImmutable?Життєвий цикл
WidgetКонфігурація UI (рецепт)ТакПерестворюється при кожному build
ElementЗв'язок Widget ↔ RenderObjectНіІснує, поки віджет у дереві
RenderObjectВідтворення та layoutНіВажкий, перевикористовується при можливості

StatelessWidget vs StatefulWidget

Flutter ділить Widget на два фундаментальні типи: StatelessWidget та StatefulWidget. StatelessWidget — це віджет, який не містить змінного стану. Зовнішній вигляд StatelessWidget повністю визначається його конструктором і не може змінитися після відтворення. Приклади: Text, Icon, Divider, Padding. Всі властивості StatelessWidget оголошуються як final у конструкторі та доступні лише для читання.

StatefulWidget — віджет із змінним станом. Він складається з двох класів: сам Widget (незмінна конфігурація, як і StatelessWidget) та State (змінний стан). Відокремлення Widget від State — ключове архітектурне рішення Flutter. Widget перестворюється при кожній збірці (build), але об'єкт State залишається жити протягом усього життєвого циклу віджета в дереві, зберігаючи свій стан.

Коли викликається setState(), Flutter позначає State як «брудний» і в наступному кадрі викликає метод build() для перебудови піддерева. Важливо: setState() не перестворює сам Widget — він лише тригерить виклик build() у існуючого State. Це означає, що StatefulWidget може оновлювати UI без втрати стану дочірніх Widget, поки ключі (Key) дочірніх елементів залишаються стабільними.

dart
// StatelessWidget — зовнішній вигляд ніколи не змінюється
class GreetingWidget extends StatelessWidget {
  const GreetingWidget({super.key, required this.name});
  final String name;

  @override
  Widget build(BuildContext context) {
    return Text('Привіт, $name');
  }
}

// StatefulWidget — лічильник із змінним станом
class CounterWidget extends StatefulWidget {
  const CounterWidget({super.key});

  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Лічильник: $_count'),
        ElevatedButton(
          onPressed: () => setState(() => _count++),
          child: const Text('Збільшити'),
        ),
      ],
    );
  }
}

Види Widget: layout, painting, interactive

Всі Widget у Flutter можна розділити на три основні категорії за функціональним призначенням. Layout Widget — відповідають за розташування дочірніх елементів на екрані. Row та Column розміщують дітей в лінію, Stack накладає один поверх іншого, Expanded та Flexible розподіляють доступний простір. Layout Widget не мають власного візуального представлення — вони керують положенням та розміром дочірніх віджетів.

Painting Widget — відповідають за візуальне оформлення. Container об'єднує декорації (колір, градієнт, тінь, межу) з layout-властивостями. Padding додає відступи, DecoratedBox малює фон, Transform застосовує трансформації (поворот, масштаб). Painting Widget є будівельними блоками візуального стилю і часто використовуються в парі з layout Widget для досягнення потрібного зовнішнього вигляду.

Interactive Widget — обробляють введення користувача. GestureDetector визначає жести (tap, swipe, pinch), InkWell додає ripple-ефект Material, TextField приймає текстове введення, Slider та Switch надають стандартні елементи керування. Interactive Widget піднімають події через callback-функції, які передаються в конструктор або обробляються через провайдери стану.

КатегоріяПриклади WidgetПризначення
LayoutRow, Column, Stack, Expanded, Flexible, AlignРозташування та розмір дочірніх елементів
PaintingContainer, Padding, DecoratedBox, RotatedBoxКолір, фон, межі, тіні, трансформації
InteractiveGestureDetector, InkWell, TextField, SliderОбробка дотиків, введення, жестів
PlatformMaterialApp, CupertinoApp, Theme, MediaQueryІнтеграція з платформою, теми, адаптація
AsyncFutureBuilder, StreamBuilder, ValueListenableBuilderРеактивне оновлення від асинхронних даних

За даними Flutter Widget of the Week (Google, 2025), спільнота Flutter активно використовує комбінацію layout + painting + interactive Widget для побудови практично будь-якого інтерфейсу. Наприклад, кнопка: InkWell (interactive) + Container (painting) + Text (static) + Padding (layout). Така модульність дозволяє перевикористовувати стандартні блоки в різних контекстах без дублювання коду.

Композиція Widget та BuildContext

Композиція Widget — це процес побудови UI через вкладення одних Widget в інші. На відміну від класичного успадкування (extends), де дочірній клас успадковує поведінку батька, Flutter використовує агрегацію: кожен Widget містить інші Widget через параметр child (для одного) або children (для кількох). Такий підхід дає більшу гнучкість та перевикористання.

BuildContext — це другий найважливіший концепт після Widget. BuildContext — це дескриптор позиції Widget в дереві елементів. Через BuildContext Widget може звертатися до Ancestor-віджетів (Theme.of(context), MediaQuery.of(context), Navigator.of(context)). BuildContext передається в метод build() і використовується для взаємодії з батьківськими та дочірніми елементами. Кожен Widget має рівно один BuildContext, який унікально ідентифікує його позицію в дереві.

За даними Flutter Architectural Overview (Google, 2025), BuildContext є основою для InheritedWidget — механізму, який дозволяє передавати дані вниз по дереву без явної передачі через конструктори. Theme, MediaQuery, Navigator та Provider використовують InheritedWidget під капотом. Будь-який deep-вкладений Widget може отримати доступ до даних предка через BuildContext.dependOnInheritedWidgetOfExactType, що робить BuildContext ключем до реактивної архітектури Flutter.

dart
// Композиція Widget через вкладення
Scaffold(
  appBar: AppBar(title: const Text('Мій застосунок')),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        const Text('Ласкаво просимо до Flutter',
          style: TextStyle(fontSize: 24)),
        const SizedBox(height: 16),
        ElevatedButton(
          onPressed: () { /* nav */ },
          child: const Text('Почати'),
        ),
      ],
    ),
  ),
)

// Доступ до тем через BuildContext
Text(
  'Стилізований текст',
  style: Theme.of(context).textTheme.headlineMedium,
)

Типові помилки з Widget

Перша і найчастіша помилка — використання StatefulWidget там, де достатньо StatelessWidget. Багато початківців Flutter-розробників створюють StatefulWidget для всіх віджетів, навіть якщо стан зберігається в зовнішньому провайдері (Provider, Riverpod, BLoC). Це надлишково і погіршує продуктивність. Правило: використовуйте StatelessWidget, якщо стан керується ззовні або якщо віджет не має власного змінного стану.

Друга помилка — створення Widget всередині методу build без const-конструктора. Кожен Widget, створений без const, алоціюється заново при кожній збірці. Якщо всередині build() створювати Widget з const-конструктором, Flutter може перевикористовувати один і той самий екземпляр, знижуючи навантаження на збирач сміття. Додавайте const скрізь, де це можливо — особливо для Text, Icon, SizedBox, Padding та інших stateless Widget.

Третя помилка — неправильна робота з ключами (Key). Flutter використовує Key для ідентифікації Widget при перебудові дерева. Якщо список Widget перебудовується без Key, Flutter може переплутати порядок елементів, що призведе до неправильної анімації або втрати стану. Завжди додавайте Key (наприклад, ValueKey або ObjectKey) для елементів у списках, особливо при використанні ListView.builder з динамічними даними.

Часті запитання

У чому різниця між StatelessWidget та StatefulWidget?

StatelessWidget — віджет без змінного стану, його зовнішній вигляд повністю визначається конструктором. StatefulWidget — віджет із змінним станом, який зберігається в окремому об'єкті State та може оновлюватися через setState() без перестворення самого віджета. Використовуйте StatelessWidget скрізь, де можливо, StatefulWidget — коли потрібен локальний стан.

Чому Widget називаються immutable?

Widget immutable — це архітектурне рішення Flutter для продуктивності. Якби Widget були mutable, Flutter не міг би безпечно порівнювати стару та нову конфігурацію при кожній збірці. Immutability дозволяє Flutter швидко визначити, чи змінився Widget (через оператор ==), та перевикористовувати існуючий RenderObject, мінімізуючи дорогі операції відтворення.

Що таке BuildContext і для чого він потрібен?

BuildContext — це дескриптор позиції Widget в дереві елементів. Через нього Widget отримує доступ до Ancestor-віджетів (Theme, MediaQuery, Navigator) та InheritedWidget. BuildContext також використовується для навігації (Navigator.of(context)), показу SnackBar та взаємодії з Provider. Кожен Widget отримує BuildContext через метод build() і передає його нащадкам.

Як вибрати між Row, Column та Stack для layout?

Використовуйте Row для горизонтального розташування елементів, Column — для вертикального, Stack — для накладання елементів один на одного. Row та Column працюють за принципом flexbox: children займають простір згідно з mainAxisSize, mainAxisAlignment та crossAxisAlignment. Stack використовує positioned-children для точного позиціонування відносно країв або центру.

Як Flutter підтримує 60 FPS з тисячами Widget?

Flutter досягає високої продуктивності за рахунок трьох механізмів: (1) Widget are cheap — легкі immutable об'єкти (40–80 байт), їх створення не навантажує GC. (2) RenderObject reuse — при зміні Widget того ж типу RenderObject перевикористовується, уникаючи дорогого перестворення. (3) Skia/Impeller engine — відтворення на C++ рівні з мінімізацією викликів малювання через repaint boundaries.

Підсумки

  • Widget — immutable конфігурація частини UI у Flutter, базовий будівельний блок, що описує зовнішній вигляд та поведінку.
  • Три дерева — Flutter використовує Widget tree (конфігурація), Element tree (зв'язок), RenderObject tree (відтворення) для оптимального рендерингу.
  • Stateless vs Stateful — StatelessWidget без стану, StatefulWidget із змінним State та методом setState() для оновлення UI.
  • Композиція — UI будується через вкладення Widget через child/children, без успадкування, що забезпечує гнучкість та перевикористання.
  • BuildContext — дескриптор позиції Widget в дереві для доступу до Theme, Navigator, MediaQuery та InheritedWidget.
  • Продуктивність — Widget перестворюються при кожному build (60 FPS), але RenderObject перевикористовується при збігу типу.
  • Категорії — Widget діляться на layout (Row, Column, Stack), painting (Container, Padding), interactive (GestureDetector, TextField) та platform (MaterialApp, Theme).

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також