1. Introduzione
Flutter è il toolkit dell'interfaccia utente di Google per creare applicazioni per dispositivi mobili, web e computer da un unico codebase. In questo codelab, creerai la seguente applicazione Flutter:
L'applicazione genera nomi dal suono accattivante, ad esempio "newstay", "lightstream", "mainbrake" o "graypine". L'utente può chiedere il nome successivo, aggiungere quello attuale ai preferiti e rivedere l'elenco dei nomi preferiti in una pagina separata. L'app è reattiva su schermi di dimensioni diverse.
Obiettivi didattici
- Nozioni di base sul funzionamento di Flutter
- Creare layout in Flutter
- Collegare le interazioni degli utenti (ad esempio la pressione dei pulsanti) al comportamento dell'app
- Mantenere organizzato il codice Flutter
- Rendere l'app reattiva (per schermi diversi)
- Aspetto coerente e la tua app
Inizierai con un'impalcatura di base in modo da poter passare direttamente alle parti interessanti.
Ed ecco Filip che ti guida nell'intero codelab.
Fai clic su Avanti per iniziare il lab.
2. Configurare l'ambiente Flutter
Editor
Per rendere questo codelab il più semplice possibile, supponiamo che utilizzerai Visual Studio Code (VS Code) come ambiente di sviluppo. È senza costi e funziona su tutte le principali piattaforme.
Naturalmente puoi utilizzare qualsiasi editor tu voglia: Android Studio, altri IDE IntelliJ, Emacs, Vim o Notepad++. Tutti funzionano con Flutter.
Ti consigliamo di utilizzare VS Code per questo codelab perché le istruzioni utilizzano per impostazione predefinita le scorciatoie specifiche di VS Code. È più facile pronunciare frasi come "fai clic qui" oppure "premi questo tasto" invece di qualcosa come "fai l'azione appropriata nell'editor per fare X".
Scegli un target di sviluppo
Flutter è un toolkit multipiattaforma. La tua app può essere eseguita su uno qualsiasi dei seguenti sistemi operativi:
- iOS
- Android
- Windows
- macOS
- Linux
- web
Tuttavia, è pratica comune scegliere un singolo sistema operativo per il quale verrà sviluppato principalmente. Si tratta del tuo "target di sviluppo", ovvero il sistema operativo su cui viene eseguita la tua app durante lo sviluppo.
Ad esempio, supponiamo che tu stia utilizzando un laptop Windows per sviluppare un'app Flutter. Se scegli Android come target di sviluppo, in genere collegherai un dispositivo Android al tuo laptop Windows tramite un cavo USB e l'app in fase di sviluppo viene eseguita su quel dispositivo Android collegato. Puoi anche scegliere Windows come destinazione di sviluppo, il che significa che la tua app in fase di sviluppo viene eseguita come app Windows insieme al tuo editor.
Potresti avere la tentazione di selezionare il web come target per lo sviluppo. Lo svantaggio di questa scelta è che perderai una delle funzionalità di sviluppo più utili di Flutter: Stateful Hot Reload. Flutter non può ricaricare le applicazioni web a caldo.
Fai la tua scelta ora. Ricorda: puoi sempre eseguire l'app su altri sistemi operativi in un secondo momento. È solo che avere un chiaro target di sviluppo in mente rende più semplice il passo successivo.
Installa Flutter
Le istruzioni più aggiornate su come installare l'SDK Flutter sono sempre disponibili all'indirizzo docs.flutter.dev.
Le istruzioni sul sito web di Flutter riguardano non solo l'installazione dell'SDK stesso, ma anche gli strumenti relativi ai target di sviluppo e i plug-in dell'editor. Ricorda che, per questo codelab, devi solo installare quanto segue:
- SDK Flutter
- Visual Studio Code con il plug-in Flutter
- Il software richiesto dal target di sviluppo scelto (ad esempio: Visual Studio per scegliere come target Windows o Xcode per scegliere macOS come target)
Nella sezione successiva creerai il tuo primo progetto Flutter.
Se hai avuto problemi finora, alcune di queste domande e risposte (di StackOverflow) potrebbero essere utili per la risoluzione dei problemi.
Domande frequenti
- Come faccio a trovare il percorso dell'SDK Flutter?
- Cosa devo fare se il comando Flutter non viene trovato?
- Come faccio a risolvere il problema "In attesa di un altro comando flutter per rilasciare il blocco di avvio" problema?
- Come faccio a comunicare a Flutter dove si trova l'installazione dell'SDK Android?
- Come posso gestire l'errore Java durante l'esecuzione di
flutter doctor --android-licenses
? - Come faccio a gestire lo strumento Android
sdkmanager
non trovato? - Come faccio a gestire il problema "Manca il componente
cmdline-tools
" errore? - Come faccio a eseguire CocoaPods su Apple Silicon (M1)?
- Come faccio a disattivare la formattazione automatica al momento del salvataggio in VS Code?
3. Creare un progetto
Crea il tuo primo progetto Flutter
Avvia Visual Studio Code e apri la tavolozza dei comandi (con F1
, Ctrl+Shift+P
o Shift+Cmd+P
). Inizia a digitare "flutter new". Seleziona il comando Flutter: New Project.
Seleziona Applicazione e una cartella in cui creare il progetto. Potrebbe essere la tua home directory o, ad esempio, C:\src\
.
Infine, assegna un nome al progetto. Ad esempio namer_app
o my_awesome_namer
.
Flutter ora crea la cartella del progetto e VS Code la apre.
Ora sovrascriverai i contenuti di tre file con uno scaffold di base dell'app.
Copia e Incolla l'app iniziale
Nel riquadro sinistro di VS Code, assicurati che Explorer sia selezionato e apri il file pubspec.yaml
.
Sostituisci il contenuto di questo file con il seguente:
pubspec.yaml
name: namer_app
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 0.0.1+1
environment:
sdk: ^3.1.1
dependencies:
flutter:
sdk: flutter
english_words: ^4.0.0
provider: ^6.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
Il file pubspec.yaml
specifica le informazioni di base della tua app, come la versione attuale, le dipendenze e gli asset con cui verrà spedita.
A questo punto, apri un altro file di configurazione nel progetto analysis_options.yaml
.
Sostituiscine i contenuti con i seguenti:
analysis_options.yaml
include: package:flutter_lints/flutter.yaml
linter:
rules:
avoid_print: false
prefer_const_constructors_in_immutables: false
prefer_const_constructors: false
prefer_const_literals_to_create_immutables: false
prefer_final_fields: false
unnecessary_breaks: true
use_key_in_widget_constructors: false
Questo file determina quanto deve essere rigoroso Flutter quando analizzi il tuo codice. Dato che questa è la tua prima incursione in Flutter, stai dicendo all'analizzatore di non esagerare. Puoi sempre modificarlo in seguito. Infatti, man mano che ti avvicini alla pubblicazione di un'effettiva app di produzione, probabilmente vorrai rendere l'analizzatore più restrittivo.
Infine, apri il file main.dart
nella directory lib/
.
Sostituisci il contenuto di questo file con il seguente:
lib/main.dart
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
return Scaffold(
body: Column(
children: [
Text('A random idea:'),
Text(appState.current.asLowerCase),
],
),
);
}
}
Queste 50 righe di codice rappresentano l'intera app fino a questo momento.
Nella sezione successiva, esegui l'applicazione in modalità di debug e inizia a svilupparla.
4. Aggiungi un pulsante
In questo passaggio viene aggiunto un pulsante Avanti per generare una nuova associazione di parole.
Avvia l'app
Per prima cosa, apri lib/main.dart
e assicurati di avere selezionato il dispositivo di destinazione. Nell'angolo in basso a destra di VS Code, troverai un pulsante che mostra il dispositivo di destinazione attuale. Fai clic per modificarlo.
Mentre lib/main.dart
è aperto, trova "riproduci" nell'angolo in alto a destra della finestra di VS Code e fai clic.
Dopo circa un minuto, l'app viene avviata in modalità di debug. Non sembra ancora molto:
Primo ricaricamento a caldo
In fondo a lib/main.dart
, aggiungi un elemento alla stringa nel primo oggetto Text
e salva il file (con Ctrl+S
o Cmd+S
). Ad esempio:
lib/main.dart
// ...
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'), // ← Example change.
Text(appState.current.asLowerCase),
],
),
);
// ...
Nota come l'app cambia immediatamente, ma la parola casuale rimane la stessa. Ecco il famoso stateful Hot Reload di Flutter al lavoro. Il ricaricamento a caldo viene attivato quando salvi le modifiche in un file di origine.
Domande frequenti
- Cosa succede se la ricarica rapida non funziona in VSCode?
- Devo premere "r" per il ricaricamento a caldo in VSCode?
- La ricarica a caldo funziona sul web?
- Come faccio a rimuovere "Debug" ?
Aggiunta di un pulsante
Poi aggiungi un pulsante in fondo a Column
, proprio sotto la seconda istanza Text
.
lib/main.dart
// ...
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'),
Text(appState.current.asLowerCase),
// ↓ Add this.
ElevatedButton(
onPressed: () {
print('button pressed!');
},
child: Text('Next'),
),
],
),
);
// ...
Quando salvi la modifica, l'app viene nuovamente aggiornata: viene visualizzato un pulsante e, quando fai clic su di esso, la console di debug in VS Code mostra il messaggio premuto!.
Un corso accelerato di Flutter in 5 minuti
Per quanto sia divertente guardare la console di debug, vuoi che il pulsante faccia qualcosa di più significativo. Prima di farlo, però, dai un'occhiata più da vicino al codice in lib/main.dart
per capire come funziona.
lib/main.dart
// ...
void main() {
runApp(MyApp());
}
// ...
Nella parte superiore del file è disponibile la funzione main()
. Nel formato attuale, indica solo a Flutter di eseguire l'app definita in MyApp
.
lib/main.dart
// ...
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
// ...
Il corso MyApp
è valido per StatelessWidget
. I widget sono gli elementi da cui crei ogni app Flutter. Come puoi vedere, anche l'app stessa è un widget.
Il codice in MyApp
configura l'intera app. Crea lo stato a livello di app (ulteriori informazioni in seguito), assegna un nome all'app, definisce il tema visivo e imposta la "casa" widget: il punto di partenza della tua app.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
// ...
Inoltre, la classe MyAppState
definisce lo stato...bene...dell'app. Questa è la tua prima incursione in Flutter, quindi questo codelab ti semplificherà e ti concentrerà. Esistono molti modi efficaci per gestire lo stato delle app in Flutter. Uno dei più facili da spiegare è ChangeNotifier
, l'approccio adottato da questa app.
MyAppState
definisce i dati necessari per il funzionamento dell'app. Al momento, contiene solo una variabile con l'attuale coppia di parole casuali. Lo aggiungerai in seguito.- La classe di stato estende
ChangeNotifier
, il che significa che può inviare una notifica ad altri utenti delle proprie modifiche. Ad esempio, se cambia la coppia di parole corrente, è necessario che alcuni widget nell'app ne siano a conoscenza. - Lo stato viene creato e fornito all'intera app utilizzando un
ChangeNotifierProvider
(vedi il codice sopra inMyApp
). In questo modo qualsiasi widget nell'app può controllare lo stato.
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) { // ← 1
var appState = context.watch<MyAppState>(); // ← 2
return Scaffold( // ← 3
body: Column( // ← 4
children: [
Text('A random AWESOME idea:'), // ← 5
Text(appState.current.asLowerCase), // ← 6
ElevatedButton(
onPressed: () {
print('button pressed!');
},
child: Text('Next'),
),
], // ← 7
),
);
}
}
// ...
Infine, c'è MyHomePage
, il widget che hai già modificato. Ogni riga numerata sotto è mappata a un commento numero riga nel codice sopra:
- Ogni widget definisce un metodo
build()
che viene chiamato automaticamente ogni volta che le circostanze del widget cambiano in modo che il widget sia sempre aggiornato. MyHomePage
monitora le modifiche allo stato attuale dell'app usando il metodowatch
.- Ogni metodo
build
deve restituire un widget o, più solitamente, un albero nidificato di widget. In questo caso, il widget di primo livello èScaffold
. Non lavorerai conScaffold
in questo codelab, ma è un widget utile e si trova nella maggior parte delle app Flutter reali. Column
è uno dei widget di layout più basilari di Flutter. Prende un numero qualsiasi di elementi secondari e li inserisce in una colonna dall'alto verso il basso. Per impostazione predefinita, la colonna posiziona visivamente i relativi elementi secondari nella parte superiore. Presto modificherai questa impostazione in modo che la colonna sia centrata.- Hai modificato questo widget
Text
nel primo passaggio. - Questo secondo widget
Text
prendeappState
e accede all'unico membro di quella classe,current
(che è unWordPair
).WordPair
offre diversi getter utili, ad esempioasPascalCase
oasSnakeCase
. In questo caso utilizziamoasLowerCase
, ma puoi cambiare ora se preferisci una delle alternative. - Nota come il codice Flutter fa un uso intensivo delle virgole finali. Questa virgola non deve essere necessariamente presente qui, perché
children
è l'ultimo (e unico) membro di questo specifico elenco di parametriColumn
. Eppure è generalmente una buona idea utilizzare le virgole finali: rendono banale l'aggiunta di altri membri e servono anche come suggerimento per il formato automatico di Dart per inserire una nuova riga. Per ulteriori informazioni, consulta la sezione Formattazione del codice.
Successivamente, collegherai il pulsante allo stato.
Il tuo primo comportamento
Scorri fino a MyAppState
e aggiungi un metodo getNext
.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
// ↓ Add this.
void getNext() {
current = WordPair.random();
notifyListeners();
}
}
// ...
Il nuovo metodo getNext()
riassegna current
con un nuovo WordPair
casuale. Chiama anche notifyListeners()
(un metodo di ChangeNotifier)
che garantisce che chiunque guardi MyAppState
riceva una notifica.
Non rimane che chiamare il metodo getNext
dal callback del pulsante.
lib/main.dart
// ...
ElevatedButton(
onPressed: () {
appState.getNext(); // ← This instead of print().
},
child: Text('Next'),
),
// ...
Salva e prova subito l'app. Ogni volta che premi il pulsante Avanti dovrebbe essere generata una nuova coppia di parole casuale.
Nella prossima sezione renderai l'interfaccia utente più bella.
5. Rendi l'app più bella
Questo è l'aspetto attuale dell'app.
Non ideale. Il pezzo forte dell'app, ovvero la coppia di parole generata in modo casuale, dovrebbe essere più visibile. Dopo tutto, è il motivo principale per cui i nostri utenti utilizzano quest'app. Inoltre, i contenuti dell'app sono stranamente decentrati e l'intera app è noioso e bianco.
Questa sezione affronta questi problemi lavorando al design dell'app. L'obiettivo finale di questa sezione è simile al seguente:
Estrarre un widget
La riga responsabile della visualizzazione della coppia di parole corrente ha ora questo aspetto: Text(appState.current.asLowerCase)
. Per modificarlo in qualcosa di più complesso, è una buona idea estrarre questa riga in un widget separato. Avere widget separati per parti logiche separate dell'interfaccia utente è un modo importante per gestire la complessità in Flutter.
Flutter fornisce un aiuto di refactoring per estrarre widget, ma prima di utilizzarlo, assicurati che la riga estratta abbia accesso solo a ciò di cui ha bisogno. Al momento, la riga accede a appState
, ma in realtà ha bisogno solo di sapere qual è l'attuale coppia di parole.
Per questo motivo, riscrivi il widget MyHomePage
come segue:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current; // ← Add this.
return Scaffold(
body: Column(
children: [
Text('A random AWESOME idea:'),
Text(pair.asLowerCase), // ← Change to this.
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
);
}
}
// ...
Bene. Il widget Text
non fa più riferimento all'intero appState
.
A questo punto, richiama il menu Refactoring. In VS Code, puoi farlo in uno dei due modi seguenti:
- Fai clic con il tasto destro del mouse sulla porzione di codice di cui vuoi eseguire il refactoring (in questo caso
Text
) e seleziona Refactoring... dal menu a discesa.
OPPURE
- Sposta il cursore sulla porzione di codice di cui vuoi eseguire il refactoring (in questo caso
Text
) e premiCtrl+.
(Win/Linux) oCmd+.
(Mac).
Nel menu Refactoring, seleziona Estrai widget. Assegna un nome, ad esempio BigCard, e fai clic su Enter
.
In questo modo viene creata automaticamente una nuova classe, BigCard
, alla fine del file corrente. Il corso ha un aspetto simile a questo:
lib/main.dart
// ...
class BigCard extends StatelessWidget {
const BigCard({
super.key,
required this.pair,
});
final WordPair pair;
@override
Widget build(BuildContext context) {
return Text(pair.asLowerCase);
}
}
// ...
Nota come l'app continua a funzionare anche durante il refactoring.
Aggiunta di una carta di credito
È il momento di trasformare questo nuovo widget nell'audace interfaccia utente che avevamo in programma all'inizio di questa sezione.
Trova la classe BigCard
e il metodo build()
al suo interno. Come prima, richiama il menu Refactor sul widget Text
. Tuttavia, questa volta non estrarrai il widget.
Seleziona invece Aggrega con spaziatura interna. Verrà creato un nuovo widget principale attorno al widget Text
chiamato Padding
. Dopo il salvataggio, noterai che la parola casuale ha già più respiro.
Aumenta la spaziatura interna dal valore predefinito di 8.0
. Ad esempio, utilizza 20
per una spaziatura interna più ampia.
Poi vai di livello più in alto. Posiziona il cursore sul widget Padding
, apri il menu Refactoring e seleziona Aggrega con widget....
Questo ti consente di specificare il widget principale. Digita "Carta" e premi Invio.
Aggrega il widget Padding
, e quindi anche Text
, con un widget Card
.
Tema e stile
Per mettere in risalto la scheda, dipingila con un colore più ricco. Inoltre, poiché è sempre opportuno mantenere una combinazione di colori coerente, utilizza Theme
dell'app per scegliere il colore.
Apporta le seguenti modifiche al metodo build()
di BigCard
.
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context); // ← Add this.
return Card(
color: theme.colorScheme.primary, // ← And also this.
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(pair.asLowerCase),
),
);
}
// ...
Queste due nuove righe fanno molto lavoro:
- Innanzitutto, il codice richiede il tema corrente dell'app con
Theme.of(context)
. - Successivamente, il codice definisce il colore della scheda in modo che sia lo stesso della proprietà
colorScheme
del tema. La combinazione di colori contiene molti colori eprimary
è il colore più evidente e distintivo dell'app.
Ora la scheda è dipinta con il colore principale dell'app:
Puoi modificare questo colore e la combinazione di colori dell'intera app scorrendo verso l'alto fino a MyApp
e modificando il colore di origine per ColorScheme
da lì.
Osserva la fluidità dell'animazione del colore. Questa operazione è chiamata animazione implicita. Molti widget Flutter si interpolano facilmente tra i valori in modo che l'UI non si limiti tra stati.
Anche il pulsante in alto sotto la scheda cambia colore. Questa è la potenza di utilizzare un valore Theme
a livello di app anziché utilizzare valori di hardcoded.
TextTheme
La scheda presenta ancora un problema: il testo è troppo piccolo e il suo colore è difficile da leggere. Per risolvere il problema, apporta le seguenti modifiche al metodo build()
di BigCard
.
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
// ↓ Add this.
final style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(20),
// ↓ Change this line.
child: Text(pair.asLowerCase, style: style),
),
);
}
// ...
Cosa c'è dietro questo cambiamento:
- Se utilizzi
theme.textTheme,
accedi al tema dei caratteri dell'app. Questo corso include membri qualibodyMedium
(per il testo standard di medie dimensioni),caption
(per i sottotitoli delle immagini) oheadlineLarge
(per i titoli grandi). - La proprietà
displayMedium
è uno stile di grandi dimensioni pensato per il testo visualizzato. In questo caso, la parola display viene usata in senso tipografico, ad esempio in caratteri tipografici di visualizzazione. La documentazione didisplayMedium
afferma che "gli stili di visualizzazione sono riservati per testo breve e importante", ovvero è esattamente il nostro caso d'uso. - In teoria, la proprietà
displayMedium
del tema potrebbe esserenull
. Dart, il linguaggio di programmazione in cui si sta scrivendo questa app, è a sicurezza null, quindi non consente di chiamare metodi di oggetti potenzialmentenull
. In questo caso, tuttavia, puoi utilizzare l'operatore!
("operatore bang") per assicurarti che Dart sappia cosa stai facendo. (displayMedium
decisamente non è null in questo caso. Il motivo per cui siamo al di fuori dell'ambito di questo codelab, però. - La chiamata di
copyWith()
sudisplayMedium
restituisce una copia dello stile di testo con le modifiche che hai definito. In questo caso, cambierai solo il colore del testo. - Per ottenere il nuovo colore, accedi di nuovo al tema dell'app. La proprietà
onPrimary
della combinazione di colori definisce un colore adatto all'uso sul colore principale dell'app.
Ora l'app dovrebbe avere il seguente aspetto:
Se vuoi, cambia ulteriormente la scheda. Ecco alcuni esempi:
copyWith()
consente di modificare molto di più dello stile del testo oltre al colore. Per ottenere l'elenco completo delle proprietà che puoi modificare, posiziona il cursore in qualsiasi punto tra le parentesi dicopyWith()
e premiCtrl+Shift+Space
(Win/Linux) oCmd+Shift+Space
(Mac).- Allo stesso modo, puoi modificare altre informazioni sul widget
Card
. Ad esempio, puoi ingrandire l'ombra della scheda aumentando il valore del parametroelevation
. - Prova a sperimentare con i colori. Oltre a
theme.colorScheme.primary
, ci sono anche.secondary
,.surface
e una miriade di altri. Tutti questi colori hanno i rispettivionPrimary
equivalenti.
Migliorare l'accessibilità
Flutter rende le app accessibili per impostazione predefinita. Ad esempio, ogni app Flutter mostra correttamente tutto il testo e gli elementi interattivi dell'app agli screen reader come TalkBack e VoiceOver.
Tuttavia, a volte è necessario svolgere un po' di lavoro. Nel caso di questa app, lo screen reader potrebbe avere problemi a pronunciare alcune coppie di parole generate. Sebbene gli esseri umani non abbiano problemi a identificare le due parole in cheaphead, uno screen reader potrebbe pronunciare ph in mezzo alla parola come f.
Una soluzione semplice è sostituire pair.asLowerCase
con "${pair.first} ${pair.second}"
. Quest'ultimo utilizza l'interpolazione di stringhe per creare una stringa (ad esempio "cheap head"
) dalle due parole contenute in pair
. L'utilizzo di due parole distinte invece di una parola composta permette di assicurarsi che gli screen reader le identifichino in modo appropriato e di offrire un'esperienza migliore agli utenti con disabilità visiva.
Tuttavia, ti consigliamo di mantenere la semplicità visiva di pair.asLowerCase
. Utilizza la proprietà semanticsLabel
di Text
per eseguire l'override dei contenuti visivi del widget di testo con un contenuto semantico più appropriato per gli screen reader:
lib/main.dart
// ...
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(20),
// ↓ Make the following change.
child: Text(
pair.asLowerCase,
style: style,
semanticsLabel: "${pair.first} ${pair.second}",
),
),
);
}
// ...
Ora gli screen reader pronunciano correttamente ogni coppia di parole generata, ma l'interfaccia utente rimane invariata. Prova a eseguire questa operazione utilizzando uno screen reader sul tuo dispositivo.
Centra l'UI
Ora che la coppia di parole casuale è presentata con un tocco visivo sufficiente, è il momento di posizionarla al centro della finestra/schermata dell'app.
Innanzitutto, ricorda che BigCard
fa parte di un Column
. Per impostazione predefinita, le colonne raggruppano i relativi elementi secondari in alto, ma possiamo facilmente ignorare questa impostazione. Vai al metodo build()
di MyHomePage
e apporta la seguente modifica:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center, // ← Add this.
children: [
Text('A random AWESOME idea:'),
BigCard(pair: pair),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
);
}
}
// ...
Centra gli elementi secondari all'interno di Column
lungo il suo asse principale (verticale).
Gli elementi secondari sono già centrati lungo l'asse trasversale della colonna (in altre parole, sono già centrati orizzontalmente). Tuttavia, Column
in sé non è centrato all'interno di Scaffold
. Possiamo verificare questa informazione utilizzando Widget Inspector.
Il Controllo widget non rientra nell'ambito di questo codelab, ma come puoi notare, quando il parametro Column
è evidenziato, non occupa l'intera larghezza dell'app. Occupa solo lo spazio orizzontale necessario ai suoi figli.
Puoi semplicemente centrare la colonna. Posiziona il cursore su Column
, richiama il menu Refactoring (con Ctrl+.
o Cmd+.
) e seleziona Aggrega con centro.
Ora l'app dovrebbe avere il seguente aspetto:
Se vuoi, puoi apportare ulteriori modifiche.
- Puoi rimuovere il widget
Text
sopraBigCard
. Si potrebbe sostenere che il testo descrittivo ("Un'idea casuale AWESOME:") non è più necessario poiché l'interfaccia utente ha senso anche senza. Ed è più pulita in questo modo. - Puoi anche aggiungere un widget
SizedBox(height: 10)
traBigCard
eElevatedButton
. In questo modo, la distanza tra i due widget è leggermente maggiore. Il widgetSizedBox
occupa spazio e non esegue automaticamente il rendering. Di solito viene utilizzato per creare "lacune" visive.
Con le modifiche facoltative, MyHomePage
contiene il seguente codice:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
),
);
}
}
// ...
L'app ha il seguente aspetto:
Nella sezione successiva, aggiungerai la possibilità di aggiungere ai preferiti (o mettere Mi piace) le parole generate.
6. Aggiungi funzionalità
L'app funziona e a volte fornisce anche coppie di parole interessanti. Ogni volta che l'utente fa clic su Avanti, ogni coppia di parole scompare per sempre. Sarebbe meglio avere un modo per "ricordare" i suggerimenti migliori, ad esempio un "Mi piace" .
Aggiungere la logica di business
Scorri fino a MyAppState
e aggiungi il codice seguente:
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
void getNext() {
current = WordPair.random();
notifyListeners();
}
// ↓ Add the code below.
var favorites = <WordPair>[];
void toggleFavorite() {
if (favorites.contains(current)) {
favorites.remove(current);
} else {
favorites.add(current);
}
notifyListeners();
}
}
// ...
Esamina le modifiche:
- Hai aggiunto una nuova proprietà a
MyAppState
chiamatafavorites
. Questa proprietà è inizializzata con un elenco vuoto:[]
. - Hai inoltre specificato che l'elenco può contenere solo coppie di parole,
<WordPair>[]
, utilizzando generics. Questo contribuisce a rendere la tua app più affidabile: Dart si rifiuta persino di eseguire l'app se provi ad aggiungere qualcosa di diverso daWordPair
. A sua volta, puoi utilizzare l'elencofavorites
sapendo che non ci saranno mai oggetti indesiderati (comenull
) nascosti al suo interno.
- Hai anche aggiunto un nuovo metodo,
toggleFavorite()
, che rimuove la coppia di parole corrente dall'elenco dei preferiti (se è già presente) o la aggiunge (se non è ancora presente). In entrambi i casi, il codice successivamente chiamanotifyListeners();
.
Aggiungi il pulsante
Con la "logica di business" è il momento di lavorare di nuovo sull'interfaccia utente. Inserire il "Mi piace" a sinistra del pulsante "Avanti" richiede un Row
. Il widget Row
è l'equivalente orizzontale di Column
, come hai visto in precedenza.
Innanzitutto, racchiudi il pulsante esistente in un Row
. Vai al metodo build()
di MyHomePage
, posiziona il cursore su ElevatedButton
, richiama il menu Refactoring con Ctrl+.
o Cmd+.
e seleziona Aggrega con riga.
Quando salvi, noterai che Row
si comporta in modo simile a Column
: per impostazione predefinita, i relativi elementi secondari sono raggruppati a sinistra. (Column
ha raggruppato i suoi figli sopra.) Per risolvere il problema, puoi utilizzare lo stesso approccio di prima, ma con mainAxisAlignment
. Tuttavia, per scopi didattici (di apprendimento), usa mainAxisSize
. Questo indica a Row
di non occupare tutto lo spazio orizzontale disponibile.
Apporta la seguente modifica:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min, // ← Add this.
children: [
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
),
);
}
}
// ...
La UI è tornata come prima.
Dopodiché, aggiungi il pulsante Mi piace e collegalo a toggleFavorite()
. Come sfida, prova innanzitutto a farlo autonomamente, senza guardare il blocco di codice riportato di seguito.
Non è un problema se non procedi esattamente come descritto di seguito. Non preoccuparti dell'icona a forma di cuore, a meno che tu non voglia una sfida impegnativa.
Va bene anche fallire, dopotutto questa è la tua prima ora con Flutter.
Ecco un modo per aggiungere il secondo pulsante a MyHomePage
. Questa volta, utilizza il costruttore ElevatedButton.icon()
per creare un pulsante con un'icona. Inoltre, nella parte superiore del metodo build
, scegli l'icona appropriata a seconda che la coppia di parole corrente sia già nei preferiti o meno. Inoltre, nota di nuovo l'utilizzo di SizedBox
, per un po' di distanza tra i due pulsanti.
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
// ↓ Add this.
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
// ↓ And this.
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
),
);
}
}
// ...
L'app dovrebbe avere il seguente aspetto:
Purtroppo l'utente non è in grado di visualizzare i preferiti. È arrivato il momento di aggiungere una schermata completamente diversa alla nostra app. Ci vediamo alla prossima sezione.
7. Aggiungi barra di navigazione
La maggior parte delle app non può contenere tutti i contenuti in un'unica schermata. Probabilmente questa app potrebbe farlo in particolare, ma a fini didattici, creerai una schermata separata per i preferiti dell'utente. Per passare da una schermata all'altra, devi implementare la prima schermata di StatefulWidget
.
Per andare all'interno di questo passaggio il prima possibile, dividi MyHomePage
in 2 widget separati.
Seleziona tutti gli elementi in MyHomePage
, eliminali e sostituiscili con il seguente codice:
lib/main.dart
// ...
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: 0,
onDestinationSelected: (value) {
print('selected: $value');
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: GeneratorPage(),
),
),
],
),
);
}
}
class GeneratorPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
);
}
}
// ...
Una volta salvato, vedrai che la parte visiva della UI è pronta, ma non funziona. Fare clic su (il cuore) nella barra di navigazione.
Esamina le modifiche.
- Innanzitutto, l'intero contenuto di
MyHomePage
viene estratti in un nuovo widget,GeneratorPage
. L'unica parte del vecchio widgetMyHomePage
che non è stata estratta èScaffold
. - Il nuovo
MyHomePage
contiene unRow
con due figli. Il primo widget èSafeArea
e il secondo è un widgetExpanded
. SafeArea
garantisce che l'elemento secondario non sia oscurato da un notch hardware o una barra di stato. In questa app, il widget si trova attorno aNavigationRail
per impedire, ad esempio, che i pulsanti di navigazione vengano oscurati da una barra di stato del dispositivo mobile.- Puoi modificare la linea
extended: false
in NavigationRail intrue
. Vengono visualizzate le etichette accanto alle icone. In un passaggio successivo, imparerai a eseguire questa operazione automaticamente quando l'app ha spazio orizzontale sufficiente. - La barra di navigazione ha due destinazioni (Casa e Preferiti) con le rispettive icone ed etichette. Definisce anche il valore
selectedIndex
corrente. Un indice selezionato pari a zero seleziona la prima destinazione, un indice selezionato di uno seleziona la seconda destinazione e così via. Per ora, è hardcoded su zero. - La barra di navigazione definisce anche cosa succede quando l'utente seleziona una delle destinazioni con
onDestinationSelected
. Al momento, l'app restituisce semplicemente il valore di indice richiesto conprint()
. - Il secondo elemento secondario di
Row
è il widgetExpanded
. I widget espansi sono estremamente utili nelle righe e nelle colonne: ti consentono di esprimere layout in cui alcuni publisher secondari occupano tutto lo spazio necessario (in questo casoSafeArea
) e altri widget dovrebbero occupare il più possibile lo spazio rimanente (in questo casoExpanded
). Un modo di pensare ai widgetExpanded
è che sono "ingordi". Se vuoi avere un'idea più chiara del ruolo di questo widget, prova a aggregare il widgetSafeArea
con un altroExpanded
. Il layout risultante ha un aspetto simile al seguente:
- Due widget
Expanded
dividono tutto lo spazio orizzontale disponibile, anche se alla barra di navigazione serviva solo una piccola sezione a sinistra. - All'interno del widget
Expanded
è presente l'elementoContainer
colorato e all'interno del contenitore, ilGeneratorPage
.
Confronto tra widget stateless e stateful
Fino ad ora, MyAppState
copreva tutte le esigenze del tuo stato. Ecco perché tutti i widget che hai scritto finora sono stateless. Non contengono alcuno stato modificabile. Nessuno dei widget può cambiare da solo; deve passare attraverso MyAppState
.
ma le cose stanno per cambiare.
Hai bisogno di un modo per mantenere il valore del valore selectedIndex
del binario di navigazione. Inoltre, vuoi poter modificare questo valore dal callback onDestinationSelected
.
Potresti aggiungere selectedIndex
come un'altra proprietà di MyAppState
. E avrebbe funzionato. Tuttavia, è possibile immaginare che lo stato dell'app crescerebbe rapidamente se ogni widget memorizzasse i propri valori al suo interno.
Alcuni stati sono rilevanti solo per un singolo widget, quindi dovrebbero rimanere con quel widget.
Inserisci StatefulWidget
, un tipo di widget con State
. Innanzitutto, converti MyHomePage
in un widget stateful.
Posiziona il cursore sulla prima riga di MyHomePage
(quella che inizia con class MyHomePage...
) e richiama il menu Refactoring utilizzando Ctrl+.
o Cmd+.
. Quindi, seleziona Converti in StatefulWidget.
L'IDE crea un nuovo corso per te, _MyHomePageState
. Questa classe estende State
e può quindi gestire i propri valori. (può cambiare da sé). Nota inoltre che il metodo build
del vecchio widget stateless è stato spostato in _MyHomePageState
(anziché rimanere nel widget). È stata eseguita parola per parola: non è stato modificato nulla all'interno del metodo build
. Ora vive solo altrove.
setState
Il nuovo widget stateful deve monitorare solo una variabile: selectedIndex
. Apporta le seguenti tre modifiche a _MyHomePageState
:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0; // ← Add this property.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex, // ← Change to this.
onDestinationSelected: (value) {
// ↓ Replace print with this.
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: GeneratorPage(),
),
),
],
),
);
}
}
// ...
Esamina le modifiche:
- Introduci una nuova variabile,
selectedIndex
, e la inizializzi in0
. - Puoi usare questa nuova variabile nella definizione di
NavigationRail
al posto della0
hardcoded che era presente fino a ora. - Quando viene chiamato il callback
onDestinationSelected
, anziché semplicemente stampare il nuovo valore nella console, lo assegni aselectedIndex
all'interno di una chiamatasetState()
. Questa chiamata è simile al metodonotifyListeners()
utilizzato in precedenza e garantisce che l'interfaccia utente si aggiorni.
La barra di navigazione ora risponde all'interazione dell'utente. Tuttavia, l'area ampliata a destra rimane la stessa. Il motivo è che il codice non utilizza selectedIndex
per determinare quale schermata viene visualizzata.
Utilizza selectedIndex
Inserisci il seguente codice all'inizio del metodo build
di _MyHomePageState
, subito prima di return Scaffold
:
lib/main.dart
// ...
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
// ...
Esamina questa porzione di codice:
- Il codice dichiara una nuova variabile,
page
, di tipoWidget
. - Quindi, un'istruzione di switch assegna una schermata a
page
, in base al valore corrente inselectedIndex
. - Poiché non è ancora disponibile
FavoritesPage
, usaPlaceholder
; un pratico widget che disegna un rettangolo incrociato ovunque lo posizioni, contrassegnando quella parte dell'interfaccia utente come incompiuta.
- Applicando il principio di fail-fast, l'istruzione switch assicura anche di generare un errore se
selectedIndex
non è né 0 né 1. In questo modo eviterai insetti in futuro. Se aggiungi una nuova destinazione alla barra di navigazione e dimentichi di aggiornare questo codice, il programma si arresta in modo anomalo in fase di sviluppo (anziché farti indovinare perché non funziona o pubblicare un codice con errori in produzione).
Ora che page
contiene il widget che vuoi mostrare sulla destra, probabilmente puoi indovinare quali altre modifiche sono necessarie.
Ecco _MyHomePageState
dopo questa singola modifica rimanente:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: false,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: page, // ← Here.
),
),
],
),
);
}
}
// ...
L'app ora passa dalla nostra GeneratorPage
al segnaposto che diventerà a breve la pagina Preferiti.
Reattività
Quindi, rendi reattiva la barra di navigazione. In altre parole, vuoi che vengano visualizzate automaticamente le etichette (utilizzando extended: true
) quando c'è abbastanza spazio.
Flutter fornisce diversi widget che ti consentono di rendere le tue app automatiche reattive. Ad esempio, Wrap
è un widget simile a Row
o Column
che aggrega automaticamente gli elementi secondari alla "riga" successiva (chiamata "corsa") quando non è disponibile abbastanza spazio verticale o orizzontale. È disponibile FittedBox
, un widget che adatta automaticamente il proprio asset secondario allo spazio disponibile in base alle tue specifiche.
Tuttavia, NavigationRail
non mostra automaticamente le etichette quando c'è spazio sufficiente perché non può sapere quale è spazio sufficiente in ogni contesto. La chiamata spetta a te, in qualità di sviluppatore.
Supponiamo che tu decida di mostrare etichette solo se MyHomePage
è larga almeno 600 pixel.
Il widget da utilizzare, in questo caso, è LayoutBuilder
. Consente di modificare la struttura dei widget in base alla quantità di spazio disponibile.
Ancora una volta, utilizza il menu Refactoring di Flutter in VS Code per apportare le modifiche necessarie. Questa volta, però, il processo è un po' più complicato.
- All'interno del metodo
build
di_MyHomePageState
, posiziona il cursore suScaffold
. - Richiama il menu Refactoring con
Ctrl+.
(Windows/Linux) oCmd+.
(Mac). - Seleziona Aggrega con Builder e premi Invio.
- Modifica il nome del
Builder
appena aggiunto inLayoutBuilder
. - Modifica l'elenco di parametri di callback da
(context)
a(context, constraints)
.
Il callback builder
di LayoutBuilder
viene chiamato ogni volta che cambiano i vincoli. Ciò si verifica, ad esempio:
- L'utente ridimensiona la finestra dell'app
- L'utente ruota il telefono dalla modalità Ritratto alla modalità Orizzontale o all'indietro
- Le dimensioni di un widget accanto a
MyHomePage
aumentano, riducendo le dimensioni dei vincoli diMyHomePage
- e così via.
Ora il tuo codice può decidere se mostrare l'etichetta eseguendo una query sull'attuale constraints
. Apporta la seguente modifica di una sola riga al metodo build
di _MyHomePageState
:
lib/main.dart
// ...
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
Widget page;
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = Placeholder();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return LayoutBuilder(builder: (context, constraints) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: constraints.maxWidth >= 600, // ← Here.
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: page,
),
),
],
),
);
});
}
}
// ...
Ora l'app reagisce all'ambiente circostante, come le dimensioni dello schermo, l'orientamento e la piattaforma. In altre parole, è reattivo.
L'unica operazione che rimane da eseguire è sostituire il file Placeholder
con una schermata Preferiti effettiva. Questo argomento verrà trattato nella prossima sezione.
8. Aggiungi una nuova pagina
Ricordi il widget Placeholder
che abbiamo utilizzato al posto della pagina Preferiti?
È il momento di risolvere il problema.
Se ti senti in vena di avventure, prova a eseguire questo passaggio autonomamente. Il tuo obiettivo è mostrare l'elenco di favorites
in un nuovo widget stateless, FavoritesPage
, quindi mostrare il widget anziché Placeholder
.
Ecco alcuni suggerimenti:
- Se vuoi che un
Column
scorra, usa il widgetListView
. - Ricorda di accedere all'istanza
MyAppState
da qualsiasi widget utilizzandocontext.watch<MyAppState>()
. - Se vuoi provare anche un nuovo widget,
ListTile
ha proprietà cometitle
(in genere per il testo),leading
(per icone o avatar) eonTap
(per le interazioni). Tuttavia, puoi ottenere effetti simili con i widget che già conosci. - Dart consente di utilizzare
for
loop all'interno dei valori letterali raccolta. Ad esempio, semessages
contiene un elenco di stringhe, puoi avere un codice simile al seguente:
Se invece hai più familiarità con la programmazione funzionale, Dart ti consente anche di scrivere codice come messages.map((m) => Text(m)).toList()
. Inoltre, ovviamente, puoi sempre creare un elenco di widget e aggiungerli in modo imperativo all'interno del metodo build
.
Il vantaggio di aggiungere personalmente la pagina Preferiti è che puoi ottenere ulteriori informazioni prendendo le tue decisioni. Lo svantaggio è che potresti incorrere in problemi che non sei ancora in grado di risolvere da solo. Ricorda: non è un problema ed è uno degli elementi più importanti dell'apprendimento. Nessuno si aspetta che tu riesca a migliorare lo sviluppo di Flutter nella tua prima ora e nemmeno tu dovresti.
Quello che segue è solo un modo per implementare la pagina dei preferiti. Il modo in cui viene implementato ti motiverà (si spera) a provare a sperimentare con il codice, migliorando l'UI e personalizzala.
Ecco il nuovo corso FavoritesPage
:
lib/main.dart
// ...
class FavoritesPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
if (appState.favorites.isEmpty) {
return Center(
child: Text('No favorites yet.'),
);
}
return ListView(
children: [
Padding(
padding: const EdgeInsets.all(20),
child: Text('You have '
'${appState.favorites.length} favorites:'),
),
for (var pair in appState.favorites)
ListTile(
leading: Icon(Icons.favorite),
title: Text(pair.asLowerCase),
),
],
);
}
}
Ecco cosa fa il widget:
- Recupera lo stato attuale dell'app.
- Se l'elenco dei preferiti è vuoto, viene visualizzato un messaggio centrato: Ancora nessun preferito*.*
- In caso contrario, viene visualizzato un elenco (con scorrimento).
- L'elenco inizia con un riepilogo (ad esempio, Hai 5 preferiti*.*).
- Il codice esegue quindi l'iterazione in tutti i preferiti e crea un widget
ListTile
per ognuno.
A questo punto non ti resta che sostituire il widget Placeholder
con un FavoritesPage
. E voilà!
Puoi trovare il codice finale di questa app nel repo di codelab su GitHub.
9. Passaggi successivi
Complimenti!
Guardati! Hai creato uno scaffold non funzionale con un Column
e due widget Text
e l'hai trasformato in una piccola app reattiva e simpatica.
Argomenti trattati
- Nozioni di base sul funzionamento di Flutter
- Creare layout in Flutter
- Collegare le interazioni degli utenti (ad esempio la pressione dei pulsanti) al comportamento dell'app
- Mantenere organizzato il codice Flutter
- Rendere reattiva la tua app
- Aspetto coerente e la tua app
E adesso?
- Sperimenta di più con l'app che hai scritto durante questo lab.
- Controlla il codice di questa versione avanzata della stessa app per scoprire come aggiungere elenchi animati, gradienti, dissolvenze incrociate e altro ancora.
- Per seguire il tuo percorso di apprendimento, vai a flutter.dev/learn.