Nella precedente lezione, abbiamo mosso i primi passi nella definizione di multi-child layout attraverso l’uso di Row
e Column
. Questi però non sono gli unici widget definiti da Flutter per creare interfacce complesse. Tra essi, infatti, c’è lo Stack Widget
che permette la sovrapposizione di più widget su layer differenti.
In questa lezione ci focalizzeremo proprio su questo componente attraverso alcuni esempi pratici, per meglio cogliere gli aspetti fondamentali e gli utilizzi di questo widget.
Stack Widget
Analogamente a quanto visto finora, lo Stack
widget può contenere molteplici widget figli che si trovano, a differenza dei widget Row
e Column
, su livelli differenti, componendo una vera e propria pila di elementi. Questo componente è quindi molto utile quando vogliamo sovrapporre diversi widget per creare interfacce nuove e articolate.
In uno Stack widget:
- il primo widget nella lista dei widget figli è quello di base;
- i widget figli successivi al primo sono sovrapposti al primo e tra loro, posizionandosi su layer differenti;
- non è possibile effettuare lo scroll dei widget figli;
- è possibile ritagliare i nodi figli che superano il box di rendering definito evitando effetti visivi poco gradevoli.
Uno degli aspetti particolari di questo questo widget è la possibilità di collocare i widget figli in una specifica posizione in base alla definizione o meno del widget Positioned
. Infatti, tutti i widget figli che devono essere collocati in una data posizione all’interno dello Stack
widget devono essere incapsulati dal widget Positioned
. Quest'ultimo definisce sei proprietà:
Proprietà | Tipo Accettato | Descrizione |
---|---|---|
bottom |
double |
rappresenta la distanza tra il bordo inferiore del nodo figlio e la parte inferiore dello Stack |
child |
Widget |
il widget da inserire nello Stack |
height |
double |
l’altezza del nodo figlio |
left |
double |
rappresenta la distanza tra il bordo sinistro del nodo figlio e il lato sinistro dello Stack |
right |
double |
rappresenta la distanza tra il il bordo destro del nodo figlio e il lato destro dello Stack |
top |
double |
rappresenta la distanza tra il bordo superiore del nodo figlio e la parte superiore dello Stack |
width |
double |
la larghezza del nodo figlio |
Attraverso queste proprietà è facile definire la posizione dei widget che devono essere messi nella pila. Tutti i widget racchiusi in un Positioned
widget e collocati in uno Stack
sono detti positioned child.
È bene precisare che non è obbligatorio usare il Positioned
widget, dal momento che lo Stack
widget è in grado di gestire comunque i propri figli collocandoli attraverso la proprietà alignment
, come vedremo negli esempi più avanti. Questi componenti, a differenza dei primi, sono detti non-positioned child.
Infine, indipendentemente dalla posizione dei widget, la dimensione dello Stack
dipende dalla dimensione del widget figlio più grande.
Le proprietà
Come tutti i widget che abbiamo visto in queste lezioni, lo Stack
widget fornisce poche ma utili proprietà per organizzare la pila di widget da mostrare. Tra queste abbiamo:
Proprietà | Tipo Accettato | Descrizione |
---|---|---|
alignment |
AlignmentGeometry |
come allineare i non-positioned child |
children |
List<Widget> |
racchiude la lista di componenti da visualizzare nella pila |
fit |
StackFit |
come devono essere dimensionati nella pila i non-positioned child. Ciò è reso possibile dall’utilizzo delle proprietà del widget StackFit |
overflow |
Overflow |
utilizza le proprietà del widget Overflow per gestire il problema di widget figli che fuoriescono dallo Stack . In generale, quando questa proprietà è settata a Overflow.clip , i figli dello Stack widget non possono essere disegnati al di fuori di esso |
textDirection |
TextDirection |
la direzione del testo |
Vediamo adesso qualche esempio pratico di utilizzo per poter sfruttare al meglio gli Stack
widget.
Esempi pratici
Per iniziare, creiamo un nuovo progetto come mostrato in una precedente lezione di questa guida e, lasciando inalterati gli import
, il metodo main()
e la classe MyApp
, cancelliamo il resto per aggiungere il seguente StatelessWidget
, composto da uno Scaffold
per la definizione dell’AppBar
e di una SingleChildScrollView
(di cui abbiamo già parlato nella lezione 11) che conterrà tutti i nostri esempi pratici di import delle immagini.
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Lesson 16'),
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
//we will add our widgets here.
],
)),
);
}
}
Modifichiamo infine la classe MyApp
per impostare il widget appena creato come home.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// . . .
home: MyPage(),
);
}
}
Ora siamo pronti per le nostre sperimentazioni.
Vediamo da subito un semplice esempio di utilizzo immaginando di voler creare un Stack
composto da tre diversi Container
di diverse dimensioni e senza una specifica posizione. Inseriamo inoltre lo Stack
all’interno di un altro Container
.
Container(
margin: EdgeInsets.only(top: 10),
child: Stack(
children: <Widget>[
// Max Size
Container(
color: Colors.orange,
height: 300,
width: 300,
),
Container(
color: Colors.yellowAccent,
height: 200.0,
width: 200.0,
),
Container(
color: Colors.greenAccent,
height: 100.0,
width: 100.0,
)
],
)),
Eseguendo l’app, possiamo notare che tutti e tre gli elementi sono posizionati nell’angolo in alto a sinistra della schermata e, grazie alla differenza di dimensioni, è possibile vedere i diversi Container
dove quello giallo è posizionato più in basso nello stack, come si evince in figura.
Per ovviare a questo problema possiamo sfruttare la proprietà alignment
. Come anticipato all’inizio della lezione, questa proprietà ci permette di posizionare lo Stack
rispetto al nodo padre. Vediamo un esempio.
Container(
margin: EdgeInsets.only(top: 10),
child: Stack(
alignment: Alignment.centerRight,
children: <Widget>[
Container(
color: Colors.orange,
height: 300,
width: 300,
),
Container(
color: Colors.yellowAccent,
height: 200.0,
width: 200.0,
),
Container(
color: Colors.greenAccent,
height: 100.0,
width: 100.0,
)
],
),
),
Eseguendo l’app, possiamo vedere che impostando la proprietà a Alignment.centerRight
lo Stack
è centrato rispetto al Container
ed è spostato sulla destra.
Una volta visto come disporre lo Stack
all’interno del widget padre, è quasi naturale chiedersi come disporre i widget figli di Stack
affinché assumano una precisa posizione all’interno dello Stack
. Per compiere questo spostamento abbiamo due possibilità:
- utilizzare il widget
Align
; - utilizzare il widget
Positioned
.
Vediamo come utilizzare entrambe le opzioni.
Prendiamo in considerazione il primo esempio mostrato in questa lezione e modifichiamo le dimensioni dei widget. Prendiamo l’ultimo Container
dello Stack
e incapsuliamolo all’interno dell’Align
widget. Impostiamo inoltre la proprietà alignment
a Alignment.topRight
per posizionare il nostro elemento in alto a destra nello Stack
.
Align(
alignment: Alignment.topRight,
child: Container(
color: Colors.greenAccent,
height: 100.0,
width: 100.0,
)
)
Il risultato di questa modifica è visibile nella seguente immagine.
Nonostante l'uso di Align
rappresenti una buona soluzione, essa non permette di avere una grande flessibilità, costringendoci a impostare manualmente i margini e la distanza dal bordo superiore e destro del Container
. Pertanto è consigliabile utilizzare al suo posto il widget Positioned
.
Analogamente a quanto fatto prima, incapsuliamo l’ultimo widget all’interno del widget Positioned
e definiamo per questo le proprietà right
e top
per spostare il Container
dai bordi come segue:
Positioned(
right: 50.0,
top: 60.0,
child: Container(
color: Colors.greenAccent,
height: 100.0,
width: 100.0,
)
)
Questa modifica ci permette di avere il risultato mostrato nella seguente figura e un maggiore controllo sul posizionamento dell’elemento nello Stack
.
Vediamo invece adesso come utilizzare la proprietà fit
dello Stack
widget. Definiamo per semplicità un metodo che avrà come parametro un possibile valore per la proprietà fit
e restituirà uno Stack
widget composto da un solo elemento (per semplificare lo scenario).
Widget _setFitStack(fit) => Stack(
fit: fit,
children: [
Container(
color: Colors.greenAccent,
height: 100.0,
width: 100.0,
)
],
);
Usiamo quindi questa funzione con i tre possibili valori che abbiamo a disposizione per la proprietà fit
, che sono:
Valore | Descrizione |
---|---|
loose |
rende i widget figli più piccoli possibile all’interno dello Stack |
expand |
imposta la dimensione dei widget figli alla più grande possibile |
passthrough |
il comportamento dipende dal nodo padre in cui è definito lo Stack |
Invochiamo quindi la funzione all’interno del nostro codice come segue.
Container(
constraints: BoxConstraints.expand(height: 160),
child: _setFitStack(StackFit.loose),
),
Container(
constraints: BoxConstraints.expand(height: 160),
child: _setFitStack(StackFit.expand),
),
Container(
color: Colors.black,
alignment: Alignment.center,
child: _setFitStack(StackFit.passthrough),
),
Come si può osservare, il metodo privato che abbiamo definito viene invocato all’interno di un apposito Container
widget di cui vengono impostati dei vincoli di altezza per i primi due widget, mentre per il terzo Container
sono state impostate alcune proprietà. Eseguendo quindi l’app, otterremo il seguente risultato.
È interessante notare come nel caso del valore loose
vengano mantenute le dimensioni originali del widget figlio del Container
, mentre col valore passthrough
il comportamento cambia in base alle proprietà definite. Nel caso mostrato, avremo che il Container
verde acqua è posto al centro mantenendo le dimensioni originali.
Vediamo infine la proprietà overflow
che permette di definire quando lasciare visibile o tagliare i widget figli il cui contenuto fuoriesce dallo Stack
.
Definiamo quindi una semplice funzione che, data la proprietà overflow
, restituirà un Container
con un’altezza massima di 38, che definisce al suo interno uno Stack
composto da un Text
widget e la proprietà overflow
in base al valore passato da parametro.
Widget _setOverflowStack(overflow) => Container(
margin: EdgeInsets.only(top: 10),
color: Colors.yellow[800],
constraints: BoxConstraints.expand(height: 38),
child: Stack(
overflow: overflow,
children: <Widget>[
Positioned(
top: 10,
child: Text(
"cogito ergo sum, cogito ergo sum, cogito ergo sum, cogito ergo sum,\ncogito ergo sum, cogito ergo sum",
style: TextStyle(color: Colors.black, fontSize: 15),
),
)
],
),
);
Invochiamo quindi il metodo appena scritto passando come parametro il valore Overflow.visible
e Overflow.clip
, ottenendo il seguente risultato.
Come si può notare, nel primo caso il testo fuoriesce dallo Stack
e dal Container
, mentre nel secondo la scritta che fuoriesce viene tagliata.
Compreso come usare le diverse proprietà, vediamo come creare uno Stack
widget di reale utilizzo in un’app. Immaginiamo che la nostra applicazione debba mostrare la foto profilo dell’utente all’interno di un cerchio e che, oltre all’immagine, debba mostrare il nome utente.
Per fare ciò, dovremo creare uno Stack
widget in cui andiamo a definire tre diversi widget figli che saranno rispettivamente:
- un
CircleAvatar
che conterrà la foto profilo mostrandola all’interno di un cerchio; - un
Container
contenente unText
widget per mostrare il nome dell’utente su uno sfondo scuro ma trasparente in modo da rendere leggibile la scritta senza oscurare l’immagine di sfondo.
Creiamo quindi un semplice metodo generico che, dato in ingresso il percorso dell’immagine e il nome dell’utente, restituirà uno Stack
widget come quello poc’anzi descritto.
Widget _buildStack(pic, name) => Stack(
alignment: const Alignment(0.6, 0.6),
children: [
CircleAvatar(
backgroundImage: AssetImage(pic),
radius: 100,
),
Container(
decoration: BoxDecoration(
color: Colors.black45,
),
child: Text(
name,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
);
In questo caso, andiamo a invocare il metodo appena creato passando come nome da mostrare e immagine quelli che abbiamo a disposizione nella cartella assets specificata nel pubspec.yaml (vedi lezione 12 per maggiori dettagli). Inoltre, per motivi grafici centriamo il widget come segue.
Center(
child: _buildStack('girl.jpg', 'Shawna'),
)
Eseguendo l’app otterremo il seguente risultato.
Il codice di questa lezione è disponibile su GitHub.