Widget — централното понятие на рамката Flutter, което описва конфигурацията на елемент от потребителския интерфейс. Всеки визуален компонент, от бутон до сложна анимация, е Widget. За разлика от други рамки, където UI се описва чрез отделни XML файлове или се рисува императивно, Flutter изгражда интерфейса чрез композиция на Widget — обединяване на малки неделими елементи в йерархично дърво. Според Flutter Documentation (2025), библиотеката Flutter SDK включва над 260 вградени Widget, покриващи Material Design, Cupertino и персонализирани стилове.
Основни точки
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 байта всеки), така че създаването им не е тясно място за производителност.
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!'),
),
),
);
}
}
За да разберете как работи Widget, трябва да се запознаете с архитектурата на Flutter, състояща се от три взаимосвързани дървета. Първото — Widget tree — описва конфигурацията на UI. Това е леко дърво, което може да бъде напълно преустроено при всеки кадър (garbage collector премахва старите Widget и създава нови). Widget са неизменими (immutable): ако цветът на текста се промени — се създава нов Text Widget с новия цвят, старият се изхвърля.
Второто дърво — 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 | Не | Тежък, използва се повторно когато е възможно |
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) на наследниците да остават стабилни.
// 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 във 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 | Предназначение |
|---|---|---|
| Layout | Row, Column, Stack, Expanded, Flexible, Align | Разположение и размер на наследниците |
| Painting | Container, Padding, DecoratedBox, RotatedBox | Цвят, фон, граници, сенки, трансформации |
| Interactive | GestureDetector, InkWell, TextField, Slider | Обработка на докосване, вход, жестове |
| Platform | MaterialApp, CupertinoApp, Theme, MediaQuery | Интеграция с платформа, теми, адаптация |
| Async | FutureBuilder, StreamBuilder, ValueListenableBuilder | Реактивно обновление от асинхронни данни |
Според данни от Flutter Widget of the Week (Google, 2025), общността на Flutter активно използва комбинация от layout + painting + interactive Widget за изграждане на почти всеки интерфейс. Например, бутон: InkWell (interactive) + Container (painting) + Text (static) + Padding (layout). Такава модулност позволява повторно използване на стандартни блокове в различни контексти без дублиране на код.
Композиция на Widget е процесът на изграждане на UI чрез влагане на един Widget в друг. За разлика от класическото наследяване (extends), при което наследникът наследява поведението на родителя, Flutter използва агрегация: всеки Widget съдържа други Widget чрез параметъра child (за един) или children (за множество). Този подход осигурява по-голяма гъвкавост и възможност за повторно използване.
BuildContext е втората най-важна концепция след Widget. BuildContext е дескриптор на позицията на Widget в дървото от елементи. Чрез BuildContext Widget може да получи достъп до предци Widget (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 под капака. Всеки дълбоко вложен Widget може да получи достъп до данни на предшественик чрез BuildContext.dependOnInheritedWidgetOfExactType, което прави BuildContext ключ към реактивната архитектура на Flutter.
// Композиция на 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,
)
Първата и най-честа грешка — използване на StatefulWidget там, където StatelessWidget е достатъчен. Много начинаещи Flutter разработчици създават StatefulWidget за всички уиджети, дори ако състоянието се съхранява във външен доставчик (Provider, Riverpod, BLoC). Това е излишно и влошава производителността. Правило: използвайте StatelessWidget, ако състоянието се управлява отвън или ако уиджетът няма собствено променливо състояние.
Втората грешка — създаване на Widget вътре в метода build без const конструктор. Всеки Widget, създаден без const, се пре разпределя при всеки build. Ако вътре в build() се създават Widget с const конструктор, Flutter може да използва повторно същата инстанция, намалявайки натоварването на garbage collector. Добавяйте const навсякъде, където е възможно — особено за Text, Icon, SizedBox, Padding и други StatelessWidget.
Третата грешка — неправилна работа с ключове (Key). Flutter използва Key за идентифициране на Widget при преустройство на дървото. Ако списък от Widget се преустройва без Key, Flutter може да обърка реда на елементите, което води до неправилна анимация или загуба на състояние. Винаги добавяйте Key (напр. ValueKey или ObjectKey) за елементи в списъци, особено при използване на ListView.builder с динамични данни.
Често задавани въпроси
StatelessWidget — уиджет без променливо състояние, външният му вид се определя изцяло от конструктора. StatefulWidget — уиджет с променливо състояние, което се съхранява в отделен обект State и може да се актуализира чрез setState() без пре създаване на самия уиджет. Използвайте StatelessWidget навсякъде, където е възможно, StatefulWidget — когато се изисква локално състояние.
Widget immutable — архитектурно решение на Flutter за производителност. Ако Widget бяха mutable, Flutter не би могъл безопасно да сравнява стара и нова конфигурация при всеки build. Неизменимостта позволява на Flutter бързо да определи дали Widget се е променил (чрез оператора ==) и да използва повторно съществуващия RenderObject, минимизирайки скъпите операции по изобразяване.
BuildContext е дескриптор на позицията на Widget в дървото от елементи. Чрез него Widget получава достъп до предци Widget (Theme, MediaQuery, Navigator) и InheritedWidget. BuildContext се използва също за навигация (Navigator.of(context)), показване на SnackBar и взаимодействие с Provider. Всеки Widget получава BuildContext чрез метода build() и го предава на своите наследници.
Използвайте Row за хоризонтално разположение на елементите, Column — за вертикално, Stack — за поставяне на елементи един върху друг. Row и Column работят на принципа на flexbox: children заемат място според mainAxisSize, mainAxisAlignment и crossAxisAlignment. Stack използва positioned-children за точно позициониране спрямо ръбовете или центъра.
Flutter постига висока производителност чрез три механизма: (1) Widget are cheap — леки immutable обекти (40–80 байта), създаването им не натоварва GC. (2) RenderObject reuse — при смяна на Widget от същия тип, RenderObject се използва повторно, избягвайки скъпо пре създаване. (3) Skia/Impeller engine — изобразяване на C++ ниво с минимизиране на извикванията за рисуване чрез repaint boundaries.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също