2.2: Primi widget di Flutter
Layout
Come possiamo realizzare un primo layout per la nostra applicazione?
Vediamo i principali Widget per gestire il layout
Container
Il Container
è un widget contenitore molto flessibile.
🔹 Attributi utili
Attributo | Descrizione |
---|---|
width , height | Imposta dimensioni fisse |
color | Colore di sfondo |
alignment | Posiziona il contenuto (Alignment.center , ecc.) |
padding | Spazio interno (usa EdgeInsets ) |
margin | Spazio esterno |
decoration | Per bordi, sfondi, gradienti |
child | Il contenuto del container |
Container(
width: 150,
height: 100,
padding: EdgeInsets.all(12),
margin: EdgeInsets.only(top: 20),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.black, width: 2),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 6,
offset: Offset(2, 2),
)
],
),
child: Text("Decorato"),
)
Center
Il Center
serve a centrare un widget rispetto al suo genitore.
🔹 Attributi utili
Attributo | Descrizione |
---|---|
child | Il widget da centrare |
heightFactor , widthFactor | Moltiplicatore sul contenuto |
Center(
child: Text("Centrato!"),
)
Padding
Descrizione
Il widget Padding
serve per aggiungere dello spazio interno (padding) intorno a un altro widget.
Può essere usato ovunque per migliorare la leggibilità, separare elementi, o allineare correttamente i contenuti nella UI.
È un contenitore invisibile che avvolge un widget figlio e gli fornisce margine interno.
Struttura di base
Padding(
padding: EdgeInsets.all(16.0),
child: Text("Ciao mondo!"),
)
Attributi utili
Attributo | Tipo | Descrizione |
---|---|---|
padding | EdgeInsets | Spazio interno da applicare (obbligatorio) |
child | Widget | Il widget contenuto nel padding |
Come si definisce EdgeInsets
?
Metodo | Significato |
---|---|
EdgeInsets.all(8) | Padding uguale su tutti i lati |
EdgeInsets.symmetric(h, v) | Padding orizzontale/verticale |
EdgeInsets.only(...) | Padding specifico per top, bottom, left, right |
📌 Esempio:
Padding(
padding: EdgeInsets.only(left: 12, top: 8),
child: Icon(Icons.star),
)
Esercizi utili
- Avvolgi 3
Text
con padding diversi (uno solo a sinistra, uno simmetrico, uno tutto attorno) e osserva come cambia il layout. - Prova l’effetto del padding mettendo un colore di sfondo al figlio
SizedBox
Descrizione
Il widget SizedBox
serve per inserire uno spazio fisso o impostare una dimensione specifica a un widget figlio.
È uno dei widget più semplici ma più utili per controllare layout e spacing.
Funziona anche senza figlio, per creare spazi vuoti.
Struttura di base
SizedBox(
width: 100,
height: 50,
child: Text("Contenuto"),
)
Attributi utili
Attributo | Tipo | Descrizione |
---|---|---|
width | double? | Larghezza del box |
height | double? | Altezza del box |
child | Widget? | Il widget da visualizzare nelle dimensioni forzate |
Uso come spazio vuoto
SizedBox(height: 20)
Molto usato in
Column
oRow
per aggiungere spazio tra widget.
Esempio pratico
Column(
children: [
Text("Titolo"),
SizedBox(height: 16),
Text("Descrizione"),
],
)
Esercizio utile
“Inserisci 3
Text
in una colonna. UsaSizedBox(height: 32)
per distanziare i primi due, eSizedBox(height: 8)
tra i secondi due. Osserva la differenza visiva.”
Widget: Spacer
Descrizione
Il widget Spacer
è usato all’interno di Row
, Column
o Flex
per occupare lo spazio disponibile in modo flessibile.
Funziona come uno “spazio vuoto espandibile” che cresce in base allo spazio residuo e al valore del suo flex
.
Utile per spingere o distribuire elementi lungo l’asse principale di un layout flessibile.
Struttura di base
Row(
children: [
Icon(Icons.star),
Spacer(),
Icon(Icons.settings),
],
)
Attributi utili
Attributo | Tipo | Descrizione |
---|---|---|
flex | int | Valore di flessibilità: maggiore = occupa più spazio |
Differenza con SizedBox
Spacer()
occupa tutto lo spazio disponibile dinamicamenteSizedBox(width: 20)
occupa uno spazio fisso e definito
Esempio pratico
Row(
children: [
Text("Indietro"),
Spacer(),
Text("Avanti"),
],
)
In questo esempio, Spacer()
spinge i due Text
agli estremi del Row
.
Esercizio utile
“In una
Row
, inserisci tre icone. Usa dueSpacer()
per distribuire le icone in modo uniforme. Prova a cambiare i valori diflex
.”
ConstrainedBox
Limita un widget a delle dimensioni minime e/o massime.
🔹 Attributi utili
Attributo | Descrizione |
---|---|
constraints | Specifica i limiti (usa BoxConstraints ) |
child | Il widget da vincolare |
ConstrainedBox(
constraints: BoxConstraints(
minWidth: 100,
maxWidth: 200,
minHeight: 50,
),
child: Container(color: Colors.amber),
)
📝 Esercitazione 1: La tua prima “Card”
Obiettivo
Creare una card centrata nello schermo che abbia:
- Un contenitore con sfondo colorato
- Bordi arrotondati
- Un po’ di padding interno
- Un testo al centro
- Una Size massima (con
ConstrainedBox
) - Un’ombra (usando
boxShadow
) - Un colore di sfondo a tua scelta
Specifiche tecniche
- Usa
Center
per centrare - Usa
ConstrainedBox
per limitare la larghezza e l’altezza a max 300px - Usa
Container
con: padding
decoration
conBoxDecoration
Text
centrato all’interno- Colori e stile a tua scelta
🕓 Tempo stimato 10–15 minuti
Layout Multipli e Scorribili in Flutter
Impariamo a usare alcuni dei widget di layout più importanti per costruire interfacce complesse e scorribili in Flutter.
🔹 Column
Un Column
organizza i widget in verticale, uno sopra l’altro.
Column(
children: [
Text("Titolo"),
ElevatedButton(onPressed: () {}, child: Text("Azione")),
],
)
Attributi utili
Attributo | Descrizione | Valori disponibili |
---|---|---|
children | Lista di widget | Qualsiasi lista di widget |
mainAxisAlignment | Allinea i figli lungo l’asse verticale | start , center , end , spaceBetween , spaceAround , spaceEvenly |
crossAxisAlignment | Allinea lungo l’asse orizzontale | start , center , end , stretch , baseline |
mainAxisSize | Spazio occupato dal Column | MainAxisSize.max (default), MainAxisSize.min |
textDirection | Direzione del testo (raro) | TextDirection.ltr , TextDirection.rtl |
verticalDirection | Direzione di crescita verticale | VerticalDirection.down (default), VerticalDirection.up |
🔹 Row
Un Row
organizza i widget in orizzontale, affiancati.
Row(
children: [
Icon(Icons.star),
Text("Preferito"),
],
)
Attributi utili
Attributo | Descrizione | Valori disponibili |
---|---|---|
children | Lista di widget | Qualsiasi lista di widget |
mainAxisAlignment | Spazio tra i figli | start , center , end , spaceBetween , spaceAround , spaceEvenly |
crossAxisAlignment | Allinea verticalmente | start , center , end , stretch , baseline |
mainAxisSize | Spazio occupato dalla Row | MainAxisSize.max , MainAxisSize.min |
textDirection | Ordine dei widget | TextDirection.ltr , TextDirection.rtl |
verticalDirection | Direzione verticale (per crossAxis ) | VerticalDirection.down , VerticalDirection.up |
🔹 Stack
Un Stack
sovrappone i widget uno sull’altro (come livelli).
Stack(
children: [
Image.asset("bg.jpg"),
Positioned(
bottom: 10,
left: 10,
child: Text("Testo sopra immagine"),
)
],
)
Attributi utili
Attributo | Descrizione | Valori disponibili |
---|---|---|
alignment | Allineamento predefinito dei figli | Alignment.center , topLeft , bottomRight , ecc. |
fit | Come i figli si adattano allo Stack | StackFit.loose (default), StackFit.expand , StackFit.passthrough |
clipBehavior | Come viene ritagliato l’overflow dei figli | Clip.none , Clip.hardEdge , Clip.antiAlias , ecc. |
children | Lista di widget sovrapposti | Qualsiasi lista di widget |
🔹 Widget speciale: Positioned
Serve per posizionare un figlio dentro uno Stack
.
Positioned(
top: 10,
left: 20,
child: Text("Posizionato"),
)
Attributi utili
Attributo | Descrizione | Valori |
---|---|---|
top | Distanza dal bordo superiore | double |
left | Distanza dal bordo sinistro | double |
right | Distanza dal bordo destro | double |
bottom | Distanza dal bordo inferiore | double |
child | Widget da posizionare | Widget |
🔹 ListView
ListView
è un elenco scorrevole verticalmente.
ListView(
children: [
ListTile(title: Text("Elemento 1")),
ListTile(title: Text("Elemento 2")),
],
)
Varianti
ListView.builder
→ per liste dinamiche e ottimizzate
Widget Utile
ListTile
→ widget standard per voci di lista
🔹 GridView
GridView
mostra i contenuti in forma di griglia, come una tabella.
GridView.count(
crossAxisCount: 2,
children: [
Container(color: Colors.blue),
Container(color: Colors.green),
],
)
Varianti
GridView.count
→ facile da usare con numero fisso di colonneGridView.builder
→ per griglie dinamiche e performanti
🔹 Expanded
Espande un widget per occupare tutto lo spazio disponibile in Row
o Column
.
Attributi utili
Attributo | Descrizione |
---|---|
flex | Peso del widget rispetto agli altri |
child | Il widget da espandere |
Row(
children: [
Expanded(
flex: 2,
child: Container(color: Colors.red),
),
Expanded(
flex: 1,
child: Container(color: Colors.green),
),
],
)
Esercizio 2: Layout base con Row, Column, Stack e Container
Obiettivo
Creare una schermata con layout semplici utilizzando:
Column
eRow
per disporre elementiStack
per sovrapporre contenutiContainer
per stile, colore e dimensioni
Cosa deve contenere
- Una
Column
con 3Container
colorati - Una
Row
con 2 box affiancati - Uno
Stack
con unContainer
di sfondo e unText
sovrapposto
Widget per il contenuto
Text
Image
In Flutter possiamo inserire immagini all’interno delle nostre schermate importando il file all’interno del progetto oppure specificando l’url per recuperarla da un server.
Abbiamo due costruttori a disposizione
- Image.network
- Image.asset
Attributi utili
Attributo | Descrizione |
---|---|
height | Settare un altezza |
width | Settare una larghezza |
fit | Determina come l’immagine si adatta allo spazio disponibile all’interno del contenitore |
alignment | Definisce come viene posizionata l’immagine all’interno del contenitore, soprattutto se l’immagine è più piccola dello spazio disponibile (es. in BoxFit.none o scaleDown) |
color | Se non è null, il colore fornito viene sovrapposto all’immagine con l’effetto specificato in colorBlendMode |
colorBlendMode | Specifica come combinare il colore (color) con l’immagine |
Image.network
Per l’esercitazione utilizzeremo il servizio online PicsumPhotos
Otteniamo un immagine random attraverso il link https://picsum.photos/500/300
Image.network(
'https://picsum.photos/500/300',
height:100,
...
)
Image.asset
Per salvare l’immagine nel pacchetto apk possiamo procedere così:
- Creare una directory
assets/images
nella directory principale - Inserire la voce
assets:
su pubspec sotto la voceflutter:
- Inserire sotto la voce
assets:
tutte le immagini o directory, che vogliamo possano essere raggiunte nel progetto, con un trattino- assets/images/
oppure- assets/images/image.png
per limitare l’accesso all’immagine
Image.asset(
'assets/images/image.png',
height:100,
...
)
Tool per capire come gli attributi modificano l’immagine
import 'package:flutter/material.dart';
void main() {
runApp(ImageFitAlignmentDemo());
}
class ImageFitAlignmentDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Image Fit & Alignment Explorer')),
body: ImageFitExplorer(),
),
);
}
}
class ImageFitExplorer extends StatefulWidget {
@override
_ImageFitExplorerState createState() => _ImageFitExplorerState();
}
class _ImageFitExplorerState extends State<ImageFitExplorer> {
BoxFit _fit = BoxFit.contain;
Alignment _alignment = Alignment.center;
BlendMode _blendMode = BlendMode.srcOver;
bool _useColor = false;
final Map<BoxFit, String> fitDescriptions = {
BoxFit.fill: "Riempi tutto lo spazio distorcendo l'immagine se necessario.",
BoxFit.contain:
"L'immagine si adatta interamente mantenendo le proporzioni.",
BoxFit.cover: "Copre tutto lo spazio ritagliando l'immagine se serve.",
BoxFit.fitWidth: "Si adatta alla larghezza mantenendo le proporzioni.",
BoxFit.fitHeight: "Si adatta all'altezza mantenendo le proporzioni.",
BoxFit.none: "L'immagine resta della dimensione originale (non scala).",
BoxFit.scaleDown: "Come 'none', ma si riduce se è troppo grande.",
};
final Map<Alignment, String> alignmentDescriptions = {
Alignment.topLeft: "Allinea in alto a sinistra.",
Alignment.topCenter: "Allinea in alto al centro.",
Alignment.topRight: "Allinea in alto a destra.",
Alignment.centerLeft: "Allinea al centro sinistra.",
Alignment.center: "Allinea perfettamente al centro.",
Alignment.centerRight: "Allinea al centro destra.",
Alignment.bottomLeft: "Allinea in basso a sinistra.",
Alignment.bottomCenter: "Allinea in basso al centro.",
Alignment.bottomRight: "Allinea in basso a destra.",
};
final Map<BlendMode, String> blendModeDescriptions = {
BlendMode.clear: "Rende trasparente la parte coperta.",
BlendMode.src: "Usa solo il colore sorgente.",
BlendMode.dst: "Mantiene solo il colore di sfondo.",
BlendMode.srcOver:
"Sovrappone il colore sorgente al colore di sfondo (default).",
BlendMode.dstOver: "Sovrappone il colore di sfondo al sorgente.",
BlendMode.srcIn:
"Mostra solo le parti dove sorgente e sfondo si sovrappongono.",
BlendMode.dstIn: "Mantiene lo sfondo solo dove c'è il sorgente.",
BlendMode.srcOut: "Mostra sorgente solo dove non c'è sfondo.",
BlendMode.dstOut: "Mantiene sfondo solo dove non c'è sorgente.",
BlendMode.srcATop:
"Mostra il sorgente sopra lo sfondo, solo nelle aree comuni.",
BlendMode.dstATop:
"Mostra sfondo sopra il sorgente, solo nelle aree comuni.",
BlendMode.xor: "Unisce sorgente e sfondo escludendo le aree comuni.",
BlendMode.plus: "Somma i colori.",
BlendMode.modulate: "Moltiplica i colori tra loro.",
BlendMode.screen: "Effetto schermo (inverso del multiply).",
BlendMode.overlay: "Combinazione tra multiply e screen.",
BlendMode.darken: "Tiene il colore più scuro tra sorgente e sfondo.",
BlendMode.lighten: "Tiene il colore più chiaro.",
BlendMode.colorDodge:
"Schiarisce lo sfondo per riflettere il colore del sorgente.",
BlendMode.colorBurn:
"Scurisce lo sfondo per riflettere il colore del sorgente.",
BlendMode.hardLight: "Combina overlay e multiply a seconda del colore.",
BlendMode.softLight: "Effetto luce morbida.",
BlendMode.difference: "Differenza assoluta tra i colori.",
BlendMode.exclusion: "Simile a difference ma meno estremo.",
BlendMode.multiply: "Moltiplica i colori tra sorgente e sfondo.",
BlendMode.hue: "Applica tonalità del sorgente al colore di sfondo.",
BlendMode.saturation: "Applica saturazione del sorgente.",
BlendMode.color: "Applica colore del sorgente, mantenendo luminosità.",
BlendMode.luminosity:
"Applica luminosità del sorgente al colore di sfondo.",
};
final List<BoxFit> fitOptions = BoxFit.values;
final List<Alignment> alignmentOptions = [
Alignment.topLeft,
Alignment.topCenter,
Alignment.topRight,
Alignment.centerLeft,
Alignment.center,
Alignment.centerRight,
Alignment.bottomLeft,
Alignment.bottomCenter,
Alignment.bottomRight,
];
final List<BlendMode> blendModes = BlendMode.values;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
children: [
Expanded(
child: Container(
width: double.infinity,
color: Colors.grey[300],
child: Image.network(
'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg',
fit: _fit,
alignment: _alignment,
color: _useColor ? Colors.red.withValues(alpha: 0.5) : null,
colorBlendMode: _blendMode,
),
),
),
SizedBox(height: 20),
Row(
children: [
Text("BoxFit:"),
SizedBox(width: 8),
DropdownButton<BoxFit>(
value: _fit,
onChanged: (value) => setState(() => _fit = value!),
items:
fitOptions
.map(
(fit) => DropdownMenuItem(
value: fit,
child: Text(fit.toString().split('.').last),
),
)
.toList(),
),
SizedBox(width: 12),
Expanded(child: Text(fitDescriptions[_fit] ?? "")),
],
),
Row(
children: [
Text("Alignment:"),
SizedBox(width: 8),
DropdownButton<Alignment>(
value: _alignment,
onChanged: (value) => setState(() => _alignment = value!),
items:
alignmentOptions
.map(
(align) => DropdownMenuItem(
value: align,
child: Text(align.toString().split('.').last),
),
)
.toList(),
),
SizedBox(width: 12),
Expanded(child: Text(alignmentDescriptions[_alignment] ?? "")),
],
),
Row(
children: [
Checkbox(
value: _useColor,
onChanged: (value) => setState(() => _useColor = value!),
),
Text("Usa Color Blend"),
SizedBox(width: 8),
DropdownButton<BlendMode>(
value: _blendMode,
onChanged: (value) => setState(() => _blendMode = value!),
items:
blendModes
.map(
(mode) => DropdownMenuItem(
value: mode,
child: Text(mode.toString().split('.').last),
),
)
.toList(),
),
SizedBox(width: 12),
Expanded(child: Text(blendModeDescriptions[_blendMode] ?? "")),
],
),
],
),
);
}
}
Icon
Descrizione
Il widget Icon
serve per visualizzare icone vettoriali in un’app Flutter.
Le icone sono scalabili, personalizzabili nel colore e nella dimensione, e spesso usate per migliorare l’usabilità dell’interfaccia.
Struttura di base
Icon(
Icons.star,
)
Attributi utili
Attributo | Tipo | Descrizione |
---|---|---|
icon | IconData | L’icona da mostrare (es. Icons.home , Icons.settings ) |
size | double? | Dimensione dell’icona in pixel logici |
color | Color? | Colore dell’icona |
semanticLabel | String? | Etichetta testuale per accessibilità |
textDirection | TextDirection? | Direzione per invertire icone bidirezionali |
Esempio pratico
Row(
children: [
Icon(Icons.location_on, color: Colors.red, size: 30),
SizedBox(width: 8),
Text("Palermo, Sicilia"),
],
)
Esercizio utile
“Crea una
Row
con 3Icon
diverse e assegna a ciascuna un colore e una dimensione diversa. Poi avvolgile in unColumn
e aggiungi unText
descrittivo sotto ognuna.”
Info utili
Una libreria di icone è un insieme di icone già pronte disponibili come costanti IconData. Flutter include già una libreria predefinita: Icons, che fa parte del pacchetto material.
ListTile
Descrizione
ListTile
è un widget utile per mostrare un singolo elemento all’interno di una lista.
Fornisce una struttura predefinita con titolo, sottotitolo, icona iniziale/finale, rendendo facile creare elenchi ordinati e coerenti.
È il mattoncino base per costruire liste (ListView
) in Flutter.
È un ottimo esempio di come un widget combinato semplifica e velocizza il lavoro
Attributi utili
Attributo | Tipo | Descrizione |
---|---|---|
title | Widget | Il contenuto principale (di solito Text ) |
subtitle | Widget? | Testo secondario sotto il title , opzionale |
leading | Widget? | Widget da mostrare all’inizio (es. Icon , CircleAvatar ) |
trailing | Widget? | Widget alla fine (es. Icon , Switch , Text ) |
onTap | Function()? | Azione da eseguire al tap |
isThreeLine | bool | Se true , il ListTile occupa tre righe (serve subtitle ) |
dense | bool? | Usa una versione più compatta (true) o più spaziosa (false) |
selected | bool | Evidenzia l’elemento come selezionato |
enabled | bool | Se false , disattiva l’interazione e mostra l’elemento in grigio |
Esempio pratico
ListTile(
leading: Icon(Icons.account_circle),
title: Text('Mario Rossi'),
subtitle: Text('Online'),
trailing: Icon(Icons.chat),
onTap: () {
print('Chat con Mario');
},
)
Esercizio utile
Crea una
ListView
con 5ListTile
, ognuno con untitle
,subtitle
,leading
(un’icona diversa) etrailing
(Icon(Icons.arrow_forward)
). Aggiungi unaonTap
che stampa il nome del contatto.
Esercizio Bonus
Crea un Widget Custom che assume lo stesso comportamento del ListTile
Wrap
Descrizione
Wrap
è un widget utile per permettere ai nostri wiget di andare a capo se non rientrano nello spazio dato dal parent
È utile per layout flessibili e dinamici, come tag, pulsanti, icone.
Attributi utili
Attributo | Tipo | Descrizione |
---|---|---|
direction | Axis | Direzione principale (horizontal o vertical ) |
alignment | WrapAlignment | Allineamento lungo l’asse principale |
runAlignment | WrapAlignment | Allineamento delle righe/colonne secondarie |
spacing | double | Spazio orizzontale tra gli elementi |
runSpacing | double | Spazio verticale tra le righe |
children | List<Widget> | Gli elementi da posizionare nel layout flessibile |
-
spacing → distanza orizzontale tra gli elementi nella stessa riga
-
runSpacing → distanza verticale tra le righe (quando vanno a capo)
Esempio pratico
Wrap(
spacing: 8,
runSpacing: 4,
children: List.generate(6, (index) {
return Chip(label: Text('Item $index'));
}),
)
Esercizio utile
“Crea un layout con almeno 10
Chip
usandoWrap
. Impostaspacing: 12
erunSpacing: 8
. Cambiaalignment
incenter
e osserva cosa succede al ridimensionare la finestra.”