Un altro componente che spesso torna utile durante lo sviluppo di un'applicazione è il dialog. Questo elemento ci permette di comunicare all'utente delle informazioni utili circa un task, e può contenere informazioni critiche, richiedere delle decisioni da parte dell'utente e coinvolgere più attività. Pertanto è conveniente utilizzarlo nel caso in cui si debba:
- segnalare all'utente un errore che interrompe il normale flusso dell'applicazione;
- mostrare all'utente delle informazioni che richiedono una sua azione, come anticipato all'inizio.
Come spesso ci è capitato di vedere in un'applicazione, i dialog sono un tipo di finestra modale che appare davanti al contenuto corrente, disabilitando tutte le funzionalità dell'applicazione e restando sullo schermo finché l'utente non ci interagisce.
Esistono diverse tipologie di dialog:
Tipo | Descrizione |
---|---|
Alert dialog | interrompe le azioni dell'utente mostrando informazioni importanti, dettagli o azioni da compiere in relazione al contesto nel quale si trova |
Simple dialog | mostra un lista di elementi alcuni dei quali potrebbero essere selezionabili dall'utente o mostra semplicemente un messaggio informativo |
Confirmation dialog | richiede all'utente di confermare una scelta |
Full-screen dialog | rispetto agli altri tipi di dialog, questo riempie completamente la schermata richiedendo all'utente di compiere una serie di operazioni, ad esempio la compilazione di una form |
Flutter mette a disposizione due tipologie di widget, SimpleDialog ed AlertDialog, che vedremo nel corso di questa lezione. Entrambe le classi estendono la classe Dialog, da cui ereditano un insieme di proprietà, e definiscono dei comportamenti specifici offerti dalle linee guida del material design.
Le classi SimpleDialog ed AlertDialog
Familiarizziamo quindi con queste due classi analizzando le principali proprietà che impiegheremo in questa lezione. Per le altre proprietà non trattate o per ulteriori informazioni su questi componenti, si rimanda alla documentazione ufficiale dei Dialog
.
Partiamo dal SimpleDialog
.
Proprietà | Tipo Accettato | Descrizione |
---|---|---|
backgroundColor |
Color |
definisce il colore di sfondo del dialog |
children |
List<Widget> |
rappresenta una lista di widget da visualizzare all'interno del dialog |
contentPadding |
EdgeInsetsGeometry |
è il padding intorno al contenuto del dialog |
elevation |
double |
rappresenta il livello di elevazione scelto per il dialog. Di default il valore è impostato a 24 e non può essere negativo |
shape |
ShapeBorder |
la forma che si vuole dare al dialog |
title |
Widget |
permette la definizione di un Widget che rappresenterà il titolo del dialog e la sua definizione è fondamentale, pur essendo opzionale, per far capire all'utente il contesto in cui si trova |
titlePadding |
EdgeInsetsGeometry |
è il padding intorno al titolo del dialog |
Molte delle proprietà appena definite per il SimpleDialog
sono valide anche per l'AlertDialog
(fatta eccezione per la proprietà children
), che in più definisce le seguenti proprietà.
Proprietà | Tipo Accettato | Descrizione |
---|---|---|
action |
List<Widget> |
definisce un insieme di azioni che l'utente può compiere. Queste verranno mostrate sulla parte inferiore del dialog |
content |
Widget |
rappresenta il contenuto che il dialog mostra nella porzione centrale del dialog. Tipicamente viene utilizzata una SingleChildScrollView per visualizzare un insieme di task che l'utente può compiere. In alternativa è possibile utilizzare un semplice Text widget |
contentTextStyle |
TextStyle |
permette di definire uno stile personalizzato per il content |
Esempi pratici
Entriamo nel vivo della lezione con alcuni esempi utili per gestire e personalizzare un SimpleDialog
e un AlertDialog
.
Per iniziare, creiamo un nuovo progetto come mostrato nella lezione 6 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 la proprietà body
, che andremo a impostare di volta in volta con la definizione di un nuovo esempio di SimpleDialog
o di AlertDialog
.
class MySimpleDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Lesson 22'),
),
body: /* set your Dialog*/,
);
}
Fatto ciò, partiamo da un esempio semplice ma efficace di SimpleDialog
. In questo caso andiamo a creare un nuovo metodo che ritorna un SimpleDialog
con un titolo e un testo.
Widget _mySimpleDialog() {
return SimpleDialog(
title: Text("Title of the Dialog"),
children: [Text("This is the text of the Dialog")],
);
}
Come si può notare, il SimpleDialog
definisce una lista di widget figli, che in questo caso abbiamo popolato come una lista composta da un Text
widget. Associando come di consueto il metodo alla proprietà body
del nostro widget MySimpleDialog
, otterremo il risultato in figura.
Figura 147. Esempio di una SimpleDialog basilare per a) Android b) iOS
Con poche righe di codice abbiamo raggiunto il nostro scopo e mostrato (staticamente) un dialog all'utente con un titolo e un testo scritto. Messaggio che giunge efficacemente, ma che risulta essere leggermente scarno dal punto di vista estetico.
Concentriamoci pertanto su come possiamo personalizzare questo componente utilizzando di volta in volta le proprietà appena discusse.
Ad esempio, immaginiamo di voler aggiungere del padding sia al titolo che ai widget definiti all'interno della proprietà children
. Per farlo, definiamo le proprietà titlePadding
e contentPadding
sia verticalmente che orizzontalmente, come di seguito.
Widget _mySimpleDialogWithPadding() {
return SimpleDialog(
title: Text("Title of the Dialog"),
titlePadding: EdgeInsets.symmetric(horizontal: 40, vertical: 50),
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 20),
children: [
Text(
"This is the text of the Dialog",
style: TextStyle(fontSize: 20),
),
Text(
"This is a second line",
style: TextStyle(fontSize: 14),
),
],
);
}
Eseguiamo quindi l'app per vedere le modifiche.
Figura 148. Utilizzo delle proprietà titlePadding e contentPadding per a) Android b) iOS
Rispetto al precedente SimpleDialog
si può notare la spaziatura rispetto al bordo sinistro e tra il titolo e i widget.
Invece, se vogliamo modificarne la forma basterà utilizzare la proprietà shape
e definire il tipo di forma che vogliamo dare al SimpleDialog
. Vediamo come.
Widget _mySimpleDialogWithCustomShape() {
return SimpleDialog(
title: Text(
"Title",
textAlign: TextAlign.center,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
children: [
Text(
"This is the text of the Dialog",
style: TextStyle(fontSize: 15),
textAlign: TextAlign.center,
)
],
);
}
In questo caso abbiamo deciso di arrotondare gli angoli del nostro SimpleDialog
, rendendo la sua forma più gradevole e molto vicina ad una Card
da mostrare all'utente. Inoltre, abbiamo centrato sia il titolo che il testo del messaggio contenuto in children
, come si può notare dalla figura.
Figura 149. Utilizzo delle proprietà shape per a) Android b) iOS
Come rimarcato più volte in questa guida, è essenziale personalizzare i widget per allinearli con lo stile della nostra applicazione. Anche i SimpleDialog
non fanno eccezione. Supponiamo quindi di voler creare un dialog che abbia:
- un titolo con un'icona;
- un testo;
- un'icona dopo il testo separata da una linea;
- i bordi arrotondati;
- un colore di sfondo;
- un'elevazione di 10.
Un insieme di caratteristiche che potrebbe risultare articolato ma che richiede il minimo sforzo per essere definito se lavoriamo con le proprietà viste finora e con quanto abbiamo appreso in queste lezioni. Vediamo come implementare il nostro SimpleDialog
.
Widget _mySimpleDialogWithCustomDetails() {
return SimpleDialog(
title: Row(
children: <Widget>[
Icon(Icons.notifications),
Text(
"Title",
)
],
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
backgroundColor: Colors.amber,
elevation: 10,
children: [
Text(
"This is the text of the Dialog",
style: TextStyle(fontSize: 15),
textAlign: TextAlign.center,
),
Divider(
thickness: 1,
),
Icon(Icons.home)
],
);
}
}
In questo esempio, per poter rappresentare il titolo affiancato da un'icona, abbiamo utilizzato il widget Row
(di cui abbiamo parlato nella lezione 16) aggiungendo alla lista di widget prima l'icona e poi il testo. Per quanto riguarda la forma, il colore di sfondo e l'elevazione, abbiamo utilizzato le proprietà shape (vista prima), backgroundColor
ed elevation
, rispettivamente. Poi per l'icona finale separata da una linea dal testo del dialog, è bastato inserire nel punto giusto il widget Divider
(visto nelle lezioni 18 e 19) come separatore di un lista di elementi.
Aggiornando la proprietà body
ed eseguendo il codice otterremo il risultato in figura.
Figura 150. Personalizzazione di un SimpleDialog per a) Android b) iOS
Ora che abbiamo compreso come personalizzare un SimpleDialog
, vediamo come mostrarlo all'utente quando clicca ad esempio su un bottone, o meglio su un RaisedButton
. Creiamo una nuova classe che chiameremo CustomDialogWithButton
e che ci servirà per creare un nuovo RaisedButton
. Di seguito lo scheletro della classe.
class CustomDialogWithButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
// . . .
}
}
Aggiungiamo a questa classe un nuovo metodo di tipo void che chiameremo customSimpleDialog
. Questo metodo sarà responsabile di costruire un nuovo SimpleDialog
, come uno di quelli visti in precedenza, e di mostrarlo all'utente finale tramite il metodo showDialog
. Vediamo come.
void customSimpleDialog(BuildContext context) {
var dialog = SimpleDialog(
title: Row(
children: <Widget>[
Icon(Icons.notifications),
Text(
"Title",
)
],
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
backgroundColor: Colors.amber,
elevation: 10,
children: [
Text(
"This is the text of the Dialog",
style: TextStyle(fontSize: 15),
textAlign: TextAlign.center,
),
Divider(
thickness: 1,
),
Icon(Icons.home)
],
);
showDialog(
context: context,
builder: (BuildContext context) {
return dialog;
});
}
In particolare, il metodo showDialog
mostrerà il dialog al di sopra del contenuto corrente dell'applicazione. Questo metodo definisce inoltre una proprietà builder
per la costruzione del Dialog
che non condividerà lo stesso context
da cui viene invocato il metodo showDialog
.
Non resta che costruire il nostro RaisedButton
.
Widget build(BuildContext context) {
return Center(
child: Container(
child: RaisedButton(
onPressed: () {
customSimpleDialog(context);
},
child: Row(children: <Widget>[
Icon(Icons.open_in_browser),
Text(
"Open Dialog",
textAlign: TextAlign.center,
)
]),
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
width: 130,
));
}
Modifichiamo infine la proprietà home della classe MyApp
come segue
home: Scaffold(
appBar: AppBar(
title: Text('Lesson 22'),
),
body: CustomDialogWithButton(),
)
al fine di inizializzare la classe appena creata e avviamo l'applicazione per vedere il frutto del nostro lavoro.
Figura 151a. Esempio di SimpleDialog al click di un RaisedButton per Android
Figura 151b. Esempio di SimpleDialog al click di un RaisedButton per iOS
Ora che abbiamo ben chiaro come utilizzare e personalizzare un SimpleDialog
non ci resta che lavorare con gli AlertDialog
. Questi widget, sebbene siano molto simili tra loro, hanno scopi ben diversi all'interno di un'applicazione, come anticipato all'inizio di questa lezione.
Creiamo un nuovo StatelessWidget
in cui andare a definire di volta in volta il nostro AlertDialog
tramite la proprietà body
.
class MyAlertDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Lesson 22'),
),
body: /* set the AlertDialog here*/,
);
}
// . . .
}
All'interno della stessa classe creiamo un nuovo metodo che chiameremo _myAlertDialog
e che mostrerà un primo esempio di AlertDialog
. In particolare il metodo si comporrà:
- di due
FlatButton
(di cui abbiamo parlato nella lezione 15) che rappresenteranno le azioni che l'utente può compiere quando gli viene mostrato l'AlertDialog
; - una variabile di tipo
AlertDialog
che definirà il nostroAlertDialog
.
In particolare, il dialog sarà composto da un titolo, un contenuto testuale e due azioni ottenute dalla definizione dei FlatButton
. Vediamo come tramite l'esempio seguente.
Widget _myAlertDialog(){
Widget cancelButton = FlatButton(
child: Text("Cancel"),
onPressed: () {
print("Cancel");
},
);
Widget okButton = FlatButton(
child: Text("Ok"),
onPressed: () {
print("Ok");
},
);
AlertDialog basicAlertDialog = AlertDialog(
title: Text("AlertDialog Title"),
content: Text("Add a message here."),
actions: [
okButton,
cancelButton,
],
);
return alertDialogWithMultipleButtons;
}
Fatto ciò, eseguiamo la nostra applicazione ottenendo il risultato in figura.
Figura 152. Esempio di un AlertDialog con due azioni per a) Android b) iOS
Ovviamente, possiamo definire più di due azioni in quanto possiamo dare alla proprietà actions una lista di Widget
. Quindi, all'interno dello stesso metodo possiamo aggiungere un terzo bottone del tipo
Widget thirdButton = FlatButton(
child: Text("The Third option"),
onPressed: () {
print("The Third option");
},
);
e aggiungerlo alla lista di actions
ottenendo il seguente risultato.
Figura 153. Esempio di un AlertDialog con tre azioni per a) Android b) iOS
Ovviamente è fortemente sconsigliato definire più di due/tre azioni per un AlertDialog
.
Personalizziamo adesso, il nostro AlertDialog
modificandone forma, elevazione e colore.
AlertDialog customAlertDialog = AlertDialog(
title: Text("AlertDialog Title"),
content: Text("Add a message here."),
actions: [
okButton,
cancelButton,
],
shape: RoundedRectangleBorder(
side: BorderSide(style: BorderStyle.none),
borderRadius: BorderRadius.circular(10)
),
elevation: 10,
backgroundColor: Colors.amber,
);
Modifichiamo il valore di ritorno della funzione con il dialog appena creato ed eseguiamo l'applicazione.
Figura 154. Esempio di un AlertDialog personalizzato per a) Android b) iOS
Immaginiamo, infine, di voler mostrare un AlertDialog
al tocco di un bottone, come fatto in precedenza per il SimpleDialog
. All'interno della classe CustomDialogWithButton
creiamo un nuovo metodo che chiameremo customAlertDialog
, per creare un nuovo AlertDialog
e mostrarlo all'utente sfruttando nuovamente la proprietà showDialog
.
void customAlertDialog(BuildContext context){
Widget cancelButton = FlatButton(
child: Text("Cancel"),
onPressed: () {
print("Cancel");
},
);
Widget okButton = FlatButton(
child: Text("Ok"),
onPressed: () {
print("Ok");
},
);
var dialog = AlertDialog(
title: Text("AlertDialog Title"),
content: Text("Add a message here."),
actions: [
okButton,
cancelButton,
],
shape: RoundedRectangleBorder(
side: BorderSide(style: BorderStyle.none),
borderRadius: BorderRadius.circular(10)
),
elevation: 10,
backgroundColor: Colors.amber,
);
showDialog(
context: context,
builder: (BuildContext context) {
return dialog;
});
}
}
Creato il nostro AlertDialog
personalizzato non resta che aggiornare la proprietà onPressed
del RaisedButton
e modificare nuovamente la proprietà home
della classe MyApp
, esattamente come fatto in precedenza.
Eseguiamo, quindi, l'applicazione per vedere il risultato delle nostre modifiche.
Figura 155a. Esempio di AlertDialog al click di un RaisedButton per Android
Figura 155b. Esempio di AlertDialog al click di un RaisedButton per iOS
Il codice di questa lezione è disponibile su GitHub.