Widget — este conceptul central al framework-ului Flutter, care descrie configurația unui element de interfață utilizator. Fiecare componentă vizuală, de la un buton până la o animație complexă, este un Widget. Spre deosebire de alte framework-uri, unde UI este descris prin fișiere XML separate sau este desenat imperativ, Flutter construiește interfața prin compoziția Widget — combinarea elementelor mici indivizibile într-un arbore ierarhic. Conform Flutter Documentation (2025), biblioteca Flutter SDK include peste 260 de Widget încorporate, acoperind Material Design, Cupertino și stiluri personalizate.
Principalele idei
Widget în Flutter este o descriere imuabilă (immutable) a unei părți a interfeței utilizator. Fiecare Widget conține proprietăți de configurare: dimensiune, culoare, poziție, text, gestionari de evenimente și Widgeturi copil. Widgeturile în sine nu sunt randate direct — ele sunt rețete (blueprints) pe baza cărora Flutter Engine creează un RenderObject — un obiect grafic real pe ecran.
Filozofia Flutter spune: „Everything is a Widget”. Aceasta înseamnă că nu doar elementele vizibile (Text, Image, Button) sunt Widgeturi, ci și blocurile structurale (Padding, Center, Column, Stack), blocurile comportamentale (GestureDetector, AnimatedBuilder) și chiar aplicația în sine (MaterialApp, CupertinoApp). Această abordare asigură uniformitate: orice element al ecranului poate fi combinat cu orice altul printr-o simplă înnestare.
Conform datelor Google I/O 2024 — Flutter Widgets Deep Dive, o aplicație medie pe Flutter conține între 200 și 1500 de Widgeturi într-un moment dat. În ciuda acestui număr, Flutter menține 60 FPS chiar și pe dispozitive bugetare datorită optimizărilor la nivel de motor C++ Skia/Impeller. Widgeturile sunt obiecte ușoare (40–80 de octeți fiecare), deci crearea lor nu este un blocaj de performanță.
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('Salut, Flutter!'),
),
),
);
}
}
Pentru a înțelege cum funcționează Widget, trebuie să cunoașteți arhitectura Flutter formată din trei arbori interconectați. Primul — Widget tree — descrie configurația UI. Este un arbore ușor care poate fi complet reconstruit la fiecare cadru (garbage collector elimină Widgeturile vechi și creează altele noi). Widgeturile sunt imuabile (immutable): dacă s-a schimbat culoarea textului — se creează un nou Widget Text cu noua culoare, cel vechi este eliminat.
Al doilea arbore — Element tree — este veriga de legătură între Widget și RenderObject. Elementul conține o referință către Widget (configurație) și către RenderObject (randare). Când Widget se schimbă, Flutter compară noul Widget cu Elementul vechi și decide: să actualizeze RenderObject-ul existent (dacă Widget este de același tip) sau să creeze unul nou (dacă tipul Widget s-a schimbat). Acest proces se numește Reconciliation și este analogul React Virtual DOM.
Al treilea arbore — RenderObject tree — este responsabil pentru randarea reală pe ecran. RenderObject conține dimensiuni concrete, poziții și metode de paint. Flutter Engine (C++ Skia sau Impeller) parcurge RenderObject tree și randeează fiecare nod. RenderObject tree este cel mai greu arbore, de aceea Flutter își minimizează modificările, reutilizând RenderObject la schimbarea Widget de același tip.
| Arbore | Scop | Imuabil? | Ciclu de viață |
|---|---|---|---|
| Widget | Configurație UI (rețetă) | Da | Recreat la fiecare build |
| Element | Legătură Widget ↔ RenderObject | Nu | Există cât timp widgetul este în arbore |
| RenderObject | Randare și layout | Nu | Grea, reutilizată când este posibil |
Flutter împarte Widgeturile în două tipuri fundamentale: StatelessWidget și StatefulWidget. StatelessWidget este un widget care nu conține stare modificabilă. Aspectul StatelessWidget este complet determinat de constructorul său și nu se poate schimba după randare. Exemple: Text, Icon, Divider, Padding. Toate proprietățile StatelessWidget sunt declarate ca final în constructor și sunt doar pentru citire.
StatefulWidget este un widget cu stare modificabilă. Este format din două clase: Widgetul în sine (configurație imuabilă, ca și StatelessWidget) și State (stare modificabilă). Separarea Widget de State este o decizie arhitecturală cheie a Flutter. Widgetul este recreat la fiecare build, dar obiectul State rămâne viu pe tot parcursul ciclului de viață al widgetului în arbore, păstrându-și starea.
Când este apelată metoda setState(), Flutter marchează State ca „murdar” și la următorul cadru apelează metoda build() pentru reconstruirea subarborelui. Important: setState() nu re-creează Widgetul în sine — declanșează doar apelul build() în State-ul existent. Aceasta înseamnă că StatefulWidget poate actualiza UI fără a pierde starea Widgeturilor copil, atâta timp cât cheile (Key) elementelor copil rămân stabile.
// StatelessWidget — aspectul nu se schimbă niciodată
class GreetingWidget extends StatelessWidget {
const GreetingWidget({super.key, required this.name});
final String name;
@override
Widget build(BuildContext context) {
return Text('Salut, $name');
}
}
// StatefulWidget — contor cu stare modificabilă
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('Contor: $_count'),
ElevatedButton(
onPressed: () => setState(() => _count++),
child: const Text('Incrementează'),
),
],
);
}
}
Toate Widgeturile în Flutter pot fi împărțite în trei categorii principale după scopul funcțional. Layout Widget — sunt responsabile pentru amplasarea elementelor copil pe ecran. Row și Column plasează copiii în linie, Stack suprapune unul peste altul, Expanded și Flexible distribuie spațiul disponibil. Layout Widgeturile nu au reprezentare vizuală proprie — ele gestionează poziția și dimensiunea widgeturilor copil.
Painting Widget — sunt responsabile pentru aspectul vizual. Container combină decorațiunile (culoare, gradient, umbră, margine) cu proprietățile de layout. Padding adaugă spațieri, DecoratedBox desenează fundalul, Transform aplică transformări (rotire, scalare). Painting Widgeturile sunt blocurile de construcție ale stilului vizual și sunt adesea folosite împreună cu layout Widgeturi pentru a obține aspectul dorit.
Interactive Widget — procesează intrarea utilizatorului. GestureDetector detectează gesturi (tap, swipe, pinch), InkWell adaugă efectul ripple Material, TextField primește intrare text, Slider și Switch oferă elemente de control standard. Interactive Widgeturile ridică evenimente prin funcții callback care sunt transmise în constructor sau gestionate prin furnizori de stare.
| Categorie | Exemple de Widget | Scop |
|---|---|---|
| Layout | Row, Column, Stack, Expanded, Flexible, Align | Amplasarea și dimensiunea elementelor copil |
| Painting | Container, Padding, DecoratedBox, RotatedBox | Culoare, fundal, margini, umbre, transformări |
| Interactive | GestureDetector, InkWell, TextField, Slider | Procesarea atingerilor, intrărilor, gesturilor |
| Platform | MaterialApp, CupertinoApp, Theme, MediaQuery | Integrare cu platforma, teme, adaptare |
| Async | FutureBuilder, StreamBuilder, ValueListenableBuilder | Actualizare reactivă din date asincrone |
Conform datelor Flutter Widget of the Week (Google, 2025), comunitatea Flutter folosește activ combinația de layout + painting + interactive Widget pentru a construi aproape orice interfață. De exemplu, un buton: InkWell (interactive) + Container (painting) + Text (static) + Padding (layout). O astfel de modularitate permite reutilizarea blocurilor standard în diferite contexte fără duplicarea codului.
Compoziția Widget este procesul de construire a UI prin înnestarea unor Widgeturi în altele. Spre deosebire de moștenirea clasică (extends), unde clasa copil moștenește comportamentul părintelui, Flutter folosește agregarea: fiecare Widget conține alte Widgeturi prin parametrul child (pentru unul) sau children (pentru mai multe). Această abordare oferă o mai mare flexibilitate și reutilizare.
BuildContext este al doilea concept cel mai important după Widget. BuildContext este un descriptor al poziției Widget în arborele de elemente. Prin BuildContext, Widget poate accesa Widgeturile ancestoare (Theme.of(context), MediaQuery.of(context), Navigator.of(context)). BuildContext este transmis în metoda build() și este folosit pentru interacțiunea cu elementele părinte și copil. Fiecare Widget are exact un BuildContext care îi identifică unic poziția în arbore.
Conform Flutter Architectural Overview (Google, 2025), BuildContext este baza pentru InheritedWidget — un mecanism care permite transmiterea datelor în josul arborelui fără transmiterea explicită prin constructori. Theme, MediaQuery, Navigator și Provider folosesc InheritedWidget sub capotă. Orice Widget profund înnestat poate accesa datele strămoșului prin BuildContext.dependOnInheritedWidgetOfExactType, ceea ce face din BuildContext cheia arhitecturii reactive Flutter.
// Compoziția Widget prin înnestare
Scaffold(
appBar: AppBar(title: const Text('Aplicația Mea')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Bun venit în Flutter',
style: TextStyle(fontSize: 24)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () { /* nav */ },
child: const Text('Începeți'),
),
],
),
),
)
// Acces la teme prin BuildContext
Text(
'Text Stilizat',
style: Theme.of(context).textTheme.headlineMedium,
)
Prima și cea mai frecventă greșeală — folosirea StatefulWidget acolo unde StatelessWidget este suficient. Mulți dezvoltatori Flutter începători creează StatefulWidget pentru toate widgeturile, chiar dacă starea este stocată într-un furnizor extern (Provider, Riverpod, BLoC). Acest lucru este redundant și degradează performanța. Regula: folosiți StatelessWidget dacă starea este gestionată din exterior sau dacă widgetul nu are propria sa stare modificabilă.
A doua greșeală — crearea Widgeturilor în interiorul metodei build fără constructor const. Fiecare Widget creat fără const este realocat la fiecare build. Dacă în interiorul build() se creează Widgeturi cu constructor const, Flutter poate reutiliza aceeași instanță, reducând încărcarea garbage collector-ului. Adăugați const peste tot unde este posibil — în special pentru Text, Icon, SizedBox, Padding și alte StatelessWidgeturi.
A treia greșeală — lucrul incorect cu cheile (Key). Flutter folosește Key pentru identificarea Widgeturilor la reconstruirea arborelui. Dacă o listă de Widgeturi este reconstruită fără Key, Flutter poate încurca ordinea elementelor, ceea ce duce la animații incorecte sau pierderea stării. Adăugați întotdeauna Key (de exemplu, ValueKey sau ObjectKey) pentru elementele din liste, în special când folosiți ListView.builder cu date dinamice.
Întrebări frecvente
StatelessWidget — widget fără stare modificabilă, aspectul său este complet determinat de constructor. StatefulWidget — widget cu stare modificabilă, care este stocată într-un obiect State separat și poate fi actualizată prin setState() fără a recrea widgetul în sine. Folosiți StatelessWidget peste tot unde este posibil, StatefulWidget — când este necesară o stare locală.
Widget immutable — este o decizie arhitecturală Flutter pentru performanță. Dacă Widgeturile ar fi mutable, Flutter nu ar putea compara în siguranță configurația veche cu cea nouă la fiecare build. Imutabilitatea permite Flutter să determine rapid dacă Widgetul s-a schimbat (prin operatorul ==) și să reutilizeze RenderObject-ul existent, minimizând operațiile costisitoare de randare.
BuildContext este un descriptor al poziției Widget în arborele de elemente. Prin el, Widgetul accesează Widgeturile ancestoare (Theme, MediaQuery, Navigator) și InheritedWidget. BuildContext este folosit și pentru navigare (Navigator.of(context)), afișarea SnackBar și interacțiunea cu Provider. Fiecare Widget primește BuildContext prin metoda build() și îl transmite moștenitorilor săi.
Folosiți Row pentru aranjarea orizontală a elementelor, Column — pentru cea verticală, Stack — pentru suprapunerea elementelor. Row și Column funcționează pe principiul flexbox: children ocupă spațiul conform mainAxisSize, mainAxisAlignment și crossAxisAlignment. Stack folosește positioned-children pentru poziționarea precisă față de margini sau centru.
Flutter atinge performanță ridicată prin trei mecanisme: (1) Widget are cheap — obiecte imuabile ușoare (40–80 de octeți), crearea lor nu încarcă GC-ul. (2) RenderObject reuse — la schimbarea Widget de același tip, RenderObject este reutilizat, evitând recrearea costisitoare. (3) Skia/Impeller engine — randarea la nivel C++ cu minimizarea apelurilor de desenare prin repaint boundaries.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și