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('Zdravo, Flutter!'),
),
),
);
}
}
Да бисте разумели како функционише Widget, потребно је да упознате архитектуру Flutter-а која се састоји од три међусобно повезана стабла. Прво — Widget tree — описује конфигурацију UI. Ово је лагано стабло које се може потпуно преобликовати при сваком кадру (garbage collector уклања старе 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 | Не | Тежак, поново се користи када је могуће |
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 — izgled se nikada ne menja
class GreetingWidget extends StatelessWidget {
const GreetingWidget({super.key, required this.name});
final String name;
@override
Widget build(BuildContext context) {
return Text('Zdravo, $name');
}
}
// StatefulWidget — brojač sa promenljivim stanjem
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('Brojač: $_count'),
ElevatedButton(
onPressed: () => setState(() => _count++),
child: const Text('Povećaj'),
),
],
);
}
}
Сви 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-а.
// Kompozicija Widget kroz umetanje
Scaffold(
appBar: AppBar(title: const Text('Moja Aplikacija')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Dobrodošli u Flutter',
style: TextStyle(fontSize: 24)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () { /* nav */ },
child: const Text('Započnite'),
),
],
),
),
)
// Pristup temama kroz BuildContext
Text(
'Stilizovani Tekst',
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 приступа прецима (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. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође