Widget Tree — це ієрархічна структура віджетів у Flutter, що визначає компонування користувацького інтерфейсу. Кожен елемент інтерфейсу, від кнопки до цілого екрана, представлений окремим віджетом, вкладеним у батьківський контейнер. Flutter оновлює Widget Tree при кожній зміні стану — фреймворк порівнює нове та старе дерево й застосовує мінімальні зміни. За даними Flutter Team, 2025, ефективна структура дерева безпосередньо впливає на плавність анімацій та швидкість відгуку інтерфейсу.
Головне
Widget Tree — це декларативний опис користувацького інтерфейсу у Flutter, побудований у вигляді дерева вкладених віджетів. Кожен віджет визначає частину UI: його конфігурацію, параметри відображення та поведінку при взаємодії. Розробник описує, як інтерфейс має виглядати при поточному стані застосунку, а Flutter відповідає за перетворення цього опису в пікселі на екрані.
На відміну від імперативних фреймворків, де розробник безпосередньо маніпулює елементами інтерфейсу, Flutter використовує декларативний підхід. При зміні стану застосунку створюється новий Widget Tree, а фреймворк обчислює різницю між старим і новим деревом. Це дозволяє мінімізувати кількість операцій відмальовування та робить код більш передбачуваним.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Widget Tree")),
body: Center(
child: Column(
children: [
Text("Привіт, Flutter"),
ElevatedButton(
onPressed: () {},
child: Text("Натисни мене"),
),
],
),
),
),
);
}
}
У цьому прикладі Widget Tree складається з MaterialApp, Scaffold, AppBar, Center, Column, Text і ElevatedButton. Кожен із цих віджетів є вузлом дерева. Коли стан застосунку змінюється, Flutter викликає метод build заново та порівнює результат з попереднім деревом.
Widget Tree починається з кореневого віджета, який передається в метод runApp. Кореневий віджет зазвичай є MaterialApp, CupertinoApp або WidgetsApp — він задає глобальні налаштування застосунку. Від кореня дерево розгалужується на дочірні віджети, кожен із яких може містити власних нащадків.
Віджети у Flutter поділяються на single-child (приймають одного нащадка через параметр child) і multi-child (приймають список нащадків через children). Приклади single-child: Center, Padding, SizedBox, Container. Multi-child: Column, Row, Stack, ListView, GridView. Ця відмінність впливає на структуру Widget Tree: multi-child віджети створюють ширші дерева, а single-child — глибші дерева.
BuildContext — це місцезнаходження віджета у Widget Tree. Кожен віджет має свій BuildContext, який передається в метод build і використовується для доступу до батьківських віджетів, теми, MediaQuery та інших InheritedWidget. BuildContext є мостом між віджетом і його елементом у Element Tree.
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final mediaQuery = MediaQuery.of(context);
return Container(
color: theme.colorScheme.primary,
child: Text(
"Ширина екрану: ${mediaQuery.size.width}"
),
);
}
}
У цьому прикладі BuildContext використовується для отримання теми та розмірів екрана. Flutter піднімається по Widget Tree до найближчого Theme та MediaQuery, які є InheritedWidget. Це демонструє, як контекст пов’язує віджет із його положенням в ієрархії.
Під час запуску Flutter-застосунку викликається функція runApp, яка приймає кореневий віджет і починає побудову Widget Tree. Процес включає три етапи: створення конфігурації віджетів, формування Element Tree та побудова RenderObject Tree для фактичного відмальовування.
Функція runApp створює кореневий елемент через WidgetsFlutterBinding, який пов’язує фреймворк із графічним двигуном. Кореневий віджет поміщається в дерево, і Flutter викликає метод build для заповнення дочірніми віджетами. Кожен виклик build породжує новий підграф Widget Tree.
Після побудови Widget Tree Flutter виконує початковий layout — обчислення розмірів і позицій усіх віджетів. Цей процес починається від кореня та поширюється вниз по дереву. Кожен віджет отримує обмеження від батька та повертає обчислений розмір. Якщо розміри не сходяться, Flutter генерує помилку layout.
Після завершення layout Flutter приступає до відмальовування кожного віджета. RenderObject перетворює опис інтерфейсу на графічні команди, які виконуються GPU через Skia або Impeller. Весь процес — від Widget Tree до пікселів — повторюється при кожному оновленні стану з частотою до 120 кадрів на секунду.
StatelessWidget — це віджет, який не має внутрішнього змінюваного стану. Його зовнішній вигляд повністю визначається вхідними параметрами, переданими через конструктор. Якщо параметри не змінилися, StatelessWidget не перебудовується. Це робить його легким з точки зору продуктивності.
Використовуйте StatelessWidget для статичних елементів інтерфейсу: іконок, текстових міток, декоративних роздільників і простих кнопок без внутрішньої логіки. За даними документації Flutter, близько 70% віджетів у типовому застосунку можуть бути StatelessWidget, що знижує навантаження на збирач сміття та прискорює rebuild.
StatefulWidget створює об’єкт State, який зберігається між перебудовами віджета. Коли стан змінюється (через виклик setState), Flutter позначає віджет як «брудний» і при наступному кадрі перебудовує його. StatefulWidget дозволяє реалізувати інтерактивні елементи: поля введення, анімації, таймери та динамічні списки.
class CounterWidget extends StatefulWidget {
@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: Text("Збільшити"),
),
],
);
}
}
У цьому прикладі StatefulWidget використовує setState для зміни лічильника. Коли стан оновлюється, Flutter перебудовує лише змінену частину Widget Tree — віджет CounterWidget та його нащадків. Батьківські віджети при цьому не перебудовуються, що є ключовою перевагою декларативної моделі Flutter.
Widget Tree є конфігураційним шаром, а Element Tree — проміжною ланкою між віджетами та фактичним відмальовуванням. Кожен віджет у Widget Tree породжує елемент у Element Tree, який зберігає посилання на віджет і керує життєвим циклом. Ця архітектура дозволяє Flutter ефективно обробляти зміни.
Коли Flutter вперше зустрічає віджет, він викликає метод createElement, який створює відповідний елемент. Для StatelessWidget створюється StatelessElement, для StatefulWidget — StatefulElement, який також створює об’єкт State. Елемент зберігається між циклами rebuild, навіть якщо віджет перестворюється.
Key — це ідентифікатор, який допомагає Flutter зіставляти віджети зі старого та нового Widget Tree. Якщо віджет має Key, Flutter використовує його для пошуку відповідного елемента, а не позицію в дереві. Ключі необхідні при роботі з динамічними списками, де порядок елементів може змінюватися.
ListView(
children: items.map((item) => ListItem(
key: ValueKey(item.id),
data: item,
)).toList(),
)
Без Key Flutter зіставляє елементи за позицією, що може призвести до неправильного збереження стану при зміні порядку. ValueKey з унікальним ідентифікатором гарантує, що кожен елемент збереже свій стан незалежно від позиції в списку.
Структура Widget Tree безпосередньо впливає на продуктивність Flutter-застосунку. Глибокі дерева з великою кількістю вкладених віджетів потребують більше часу на layout-фазу та збільшують обсяг пам’яті. Flutter DevTools надає інструменти для аналізу Widget Tree та виявлення вузьких місць.
Кожен рівень вкладеності додає додаткові обчислення при layout і paint. Замість глибокої в chain вкладеності використовуйте більш пласкі структури. Наприклад, Row з Expanded може замінити кілька вкладених Containers з Align. За даними Flutter Team, оптимізація дерева може скоротити час layout до 40%.
Flutter DevTools надає віджет «Widget Inspector», який показує поточне Widget Tree в реальному часі. Розробник може вибрати будь-який віджет на екрані та побачити його місце в дереві, параметри та обмеження layout. Це допомагає виявити несподівану вкладеність, надмірні rebuild та проблеми з розмірами.
RepaintBoundary — це віджет, який ізолює частину Widget Tree для самостійного відмальовування. Якщо вміст RepaintBoundary змінюється, перемальовується лише його область, а не весь екран. Використовуйте RepaintBoundary для анімацій, списків та інших часто оновлюваних елементів.
RepaintBoundary(
child: CustomPaint(
painter: MyPainter(),
child: SizedBox(
width: 200,
height: 200,
),
),
)
У цьому прикладі RepaintBoundary ізолює CustomPaint в окрему область відмальовування. При оновленні анімації всередині цієї області перемальовується лише віджет CustomPaint, а решта екрана залишається без змін. Це особливо корисно в складних інтерфейсах з безліччю анімованих елементів.
Часті запитання
Widget Tree — це декларативний опис інтерфейсу, який перестворюється при кожному rebuild. Element Tree зберігається між оновленнями та керує життєвим циклом, станом і зіставленням віджетів з фактичними RenderObject.
Обмежень на кількість віджетів немає, але на практиці дерево з тисячами віджетів може сповільнити layout-фазу. Flutter оптимізований для дерев до кількох тисяч вузлів; при більшій кількості рекомендується віртуалізація через ListView.builder.
Використовуйте Flutter DevTools — вкладку «Widget Inspector». Запустіть застосунок у debug-режимі, відкрийте DevTools у браузері та виберіть будь-який віджет на екрані, щоб побачити його місце в Widget Tree.
Rebuild — це процес перестворення конфігурації віджетів при зміні стану. Flutter викликає метод build заново для змінених віджетів, порівнює нове Widget Tree з попереднім і застосовує мінімальні зміни до Element Tree.
Зменшуйте глибину вкладеності, використовуйте const-віджети для статичних елементів, застосовуйте RepaintBoundary для ізоляції анімацій і уникайте надмірних StatefulWidget там, де достатньо StatelessWidget.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також