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('Zdravo, Flutter!'),
        ),
      ),
    );
  }
}

Три стабла Flutter-а: Widget, Element и RenderObject

Да бисте разумели како функционише 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НеТежак, поново се користи када је могуће

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 — 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: 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 може приступити прецима 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-а.

dart
// 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,
)

Типичне грешке са Widget

Прва и најчешћа грешка — коришћење 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?

StatelessWidget — виџет без променљивог стања, његов изглед је потпуно одређен конструктором. StatefulWidget — виџет са променљивим стањем које се чува у засебном објекту State и може се ажурирати кроз setState() без поновног креирања самог виџета. Користите StatelessWidget свуда где је могуће, StatefulWidget — када је потребно локално стање.

Зашто се Widget називају immutable?

Widget immutable — архитектурна одлука Flutter-а за перформансе. Да су Widget променљиви (mutable), Flutter не би могао безбедно да пореди стару и нову конфигурацију при сваком build. Непроменљивост омогућава Flutter-у да брзо утврди да ли се Widget променио (кроз оператор ==) и поново искористи постојећи RenderObject, минимизирајући скупе операције рендеровања.

Шта је BuildContext и чему служи?

BuildContext је дескриптор положаја Widget у стаблу елемената. Кроз њега Widget приступа прецима (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. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође