1. Introduzione
Flutter è il toolkit UI di Google per la creazione di applicazioni per mobile, web e desktop da un unico codebase. In questo codelab, creerai la seguente applicazione Flutter:
L'applicazione genera nomi accattivanti, come "newstay", "lightstream", "mainbrake" o "graypine". L'utente può chiedere il nome successivo, aggiungere quello corrente ai preferiti ed esaminare l'elenco dei preferiti in una pagina separata. L'app è adattabile a schermi di dimensioni diverse.
Obiettivi didattici
- Nozioni di base sul funzionamento di Flutter
- Creazione di layout in Flutter
- Collegamento delle interazioni degli utenti (ad esempio le pressioni dei pulsanti) al comportamento dell'app
- Organizzare il codice Flutter
- Rendere l'app adattabile (per schermi diversi)
- Ottenere un aspetto coerente della tua app
Inizierai con uno scheletro di base per poter passare direttamente alle parti interessanti.
E ora Filip ti guiderà attraverso l'intero codelab.
Fai clic su Avanti per avviare il lab.
2. Configurare l'ambiente Flutter
Editor
Per semplificare al massimo questo codelab, presupponiamo che tu utilizzi Visual Studio Code (VS Code) come ambiente di sviluppo. È senza costi e funziona su tutte le principali piattaforme.
Ovviamente puoi utilizzare qualsiasi editor che preferisci: 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 prevedono per impostazione predefinita le scorciatoie specifiche di VS Code. È più facile dire "fai clic qui" o "premi questo tasto" anziché "esegui l'azione appropriata nell'editor per eseguire X".
Scegli una destinazione di sviluppo
Flutter è un toolkit multipiattaforma. La tua app può essere eseguita su uno dei seguenti sistemi operativi:
- iOS
- Android
- Windows
- macOS
- Linux
- web
Tuttavia, è prassi comune scegliere un singolo sistema operativo per cui svilupperai principalmente. Si tratta del "target di sviluppo", ovvero il sistema operativo su cui viene eseguita l'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 colleghi un dispositivo Android al laptop Windows con un cavo USB e l'app in fase di sviluppo viene eseguita sul dispositivo Android collegato. Puoi anche scegliere Windows come target di sviluppo, il che significa che l'app in fase di sviluppo viene eseguita come app per Windows insieme all'editor.
Potrebbe essere allettante selezionare il web come target di sviluppo. Lo svantaggio di questa scelta è che perdi una delle funzionalità di sviluppo più utili di Flutter: il ricaricamento caldo con stato. Flutter non può ricaricare le applicazioni web a caldo.
Scegli subito. Ricorda: puoi sempre eseguire la tua app su altri sistemi operativi in un secondo momento. È solo che avere un obiettivo di sviluppo chiaro in mente rende più agevole il passaggio 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 al target di sviluppo e i plug-in dell'editor. Ricorda che, per questo codelab, devi installare solo 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 il target Windows o Xcode per il target macOS)
Nella sezione successiva, creerai il tuo primo progetto Flutter.
Se hai riscontrato problemi finora, alcune di queste domande e risposte (di StackOverflow) potrebbero esserti 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 all'avvio"?
- Come faccio a indicare a Flutter la posizione dell'installazione dell'SDK Android?
- Come faccio a 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 l'errore "Componente
cmdline-tools
mancante"? - Come faccio a eseguire CocoaPods su Apple Silicon (M1)?
- Come faccio a disattivare la formattazione automatica al salvataggio in VS Code?
3. Crea un progetto
Creare il primo progetto Flutter
Avvia Visual Studio Code e apri la tavolozza dei comandi (con F1
o Ctrl+Shift+P
o Shift+Cmd+P
). Inizia a digitare "flutter new". Seleziona il comando Flutter: New Project (Flutter: nuovo progetto).
Quindi, seleziona Application (Applicazione) e una cartella in cui creare il progetto. Potrebbe trattarsi della tua home directory o di qualcosa di simile a C:\src\
.
Infine, assegna un nome al progetto. Ad esempio namer_app
o my_awesome_namer
.
Ora Flutter crea la cartella del progetto e VS Code la apre.
Ora sovrascrivi i contenuti di tre file con uno scheletro di base dell'app.
Copia e incolla l'app iniziale
Nel riquadro a sinistra di VS Code, assicurati che sia selezionato Esplora e apri il file pubspec.yaml
.
Sostituisci i contenuti di questo file con quanto segue:
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.6.0
dependencies:
flutter:
sdk: flutter
english_words: ^4.0.0
provider: ^6.1.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true
Il file pubspec.yaml
specifica informazioni di base sulla tua app, ad esempio la versione corrente, le dipendenze e gli asset con cui verrà rilasciata.
Apri un altro file di configurazione nel progetto, analysis_options.yaml
.
Sostituisci i contenuti con quanto segue:
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 il grado di severità di Flutter durante l'analisi del codice. Dato che è la tua prima incursione in Flutter, stai dicendo all'analizzatore di non preoccuparsi. Puoi sempre modificarlo in un secondo momento. Infatti, man mano che ti avvicini alla pubblicazione di un'app di produzione effettiva, quasi certamente vorrai rendere l'analizzatore più rigoroso.
Infine, apri il file main.dart
nella directory lib/
.
Sostituisci i contenuti di questo file con quanto segue:
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 costituiscono l'intera app finora.
Nella sezione successiva, esegui l'applicazione in modalità di debug e inizia a sviluppare.
4. Aggiungere un pulsante
Questo passaggio aggiunge un pulsante Avanti per generare una nuova accoppiata di parole.
Avvia l'app
Innanzitutto, apri lib/main.dart
e assicurati di aver selezionato il dispositivo di destinazione. Nell'angolo in basso a destra di VS Code, troverai un pulsante che mostra il dispositivo di destinazione corrente. Fai clic per modificarla.
Mentre lib/main.dart
è aperto, individua il pulsante "Riproduci" nell'angolo in alto a destra della finestra di VS Code e fai clic su di esso.
Dopo circa un minuto, l'app viene avviata in modalità di debug. Non sembra molto, ma:
Primo ricaricamento rapido
Nella parte inferiore di lib/main.dart
, aggiungi qualcosa 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 invariata. Questo è il famoso ricoricamento caldo stateful di Flutter al lavoro. Il ricaricamento rapido viene attivato quando salvi le modifiche a un file di origine.
Domande frequenti
- Cosa succede se il ricaricamento rapido non funziona in VSCode?
- Devo premere "r" per il ricaricamento rapido in VSCode?
- Il ricaricamento rapido funziona sul web?
- Come faccio a rimuovere il banner "Debug"?
Aggiunta di un pulsante
Aggiungi un pulsante nella parte inferiore di Column
, proprio sotto la seconda istanza di 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 si aggiorna di nuovo: viene visualizzato un pulsante e, quando fai clic su di esso, la console di debug in VS Code mostra il messaggio Pulsante premuto.
Un corso introduttivo a Flutter in 5 minuti
Anche se è divertente guardare la Console di debug, vorresti che il pulsante facesse qualcosa di più significativo. Prima di procedere, 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 troverai la funzione main()
. Nella sua forma attuale, dice a Flutter solo 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(),
),
);
}
}
// ...
La classe MyApp
estende StatelessWidget
. I widget sono gli elementi da cui viene creata 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 (di seguito sono riportate ulteriori informazioni), assegna un nome all'app, definisce il tema visivo e imposta il widget "home", ovvero il punto di partenza dell'app.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
// ...
Poi, la classe MyAppState
definisce lo stato dell'app. Se è la tua prima esperienza con Flutter, questo codelab manterrà la formazione semplice e mirata. Esistono molti modi efficaci per gestire lo stato dell'app in Flutter. Uno dei più semplici da spiegare è ChangeNotifier
, l'approccio adottato da questa app.
MyAppState
definisce i dati di cui l'app ha bisogno per funzionare. Al momento contiene una sola variabile con la coppia di parole casuali corrente. Aggiungerai altro in un secondo momento.- La classe di stato estende
ChangeNotifier
, il che significa che può notificare ad altri le proprie modifiche. Ad esempio, se la coppia di parole corrente cambia, alcuni widget nell'app devono saperlo. - Lo stato viene creato e fornito all'intera app utilizzando un
ChangeNotifierProvider
(vedi il codice sopra inMyApp
). In questo modo, qualsiasi widget dell'app può acquisire 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 di seguito corrisponde a un commento con il numero di riga nel codice riportato 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
tiene traccia delle modifiche allo stato corrente dell'app utilizzando il metodowatch
.- Ogni metodo
build
deve restituire un widget o (più in genere) un albero nidificato di widget. In questo caso, il widget di primo livello èScaffold
. In questo codelab non lavorerai conScaffold
, ma si tratta di un widget utile che si trova nella maggior parte delle app Flutter reali. Column
è uno dei widget di layout più semplici di Flutter. Prende un numero qualsiasi di elementi secondari e li inserisce in una colonna da cima a fondo. Per impostazione predefinita, la colonna posiziona visivamente i relativi elementi secondari in alto. A breve cambierai 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
fornisce diversi getter utili, comeasPascalCase
oasSnakeCase
. Qui utilizziamoasLowerCase
, ma puoi modificarlo subito se preferisci una delle alternative. - Nota come il codice Flutter fa un uso intensivo delle virgole finali. Questa virgola specifica non è necessaria, perché
children
è l'ultimo (e anche l'unico) membro di questo elenco di parametriColumn
. Tuttavia, in genere è buona norma utilizzare le virgole finali: semplificano l'aggiunta di altri membri e servono anche da suggerimento per l'autoformattazione di Dart per inserire un a capo. Per ulteriori informazioni, consulta la sezione Formattazione del codice.
A questo punto, dovrai collegare 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. Inoltre, chiama notifyListeners()
(un metodo di ChangeNotifier)
che garantisce che chiunque guardi MyAppState
riceva una notifica.
Non resta 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. Dovrebbe generare una nuova coppia di parole casuali ogni volta che premi il pulsante Avanti.
Nella sezione successiva, renderai più bella l'interfaccia utente.
5. Rendere l'app più bella
Questo è l'aspetto attuale dell'app.
Non male. Il fulcro dell'app, ovvero la coppia di parole generata in modo casuale, deve essere più visibile. Dopotutto, è il motivo principale per cui i nostri utenti utilizzano questa app. Inoltre, i contenuti dell'app sono stranamente fuori centro e l'intera app è in bianco e nero.
Questa sezione risolve questi problemi lavorando sul design dell'app. L'obiettivo finale di questa sezione è il seguente:
Estrarre un widget
La riga responsabile della visualizzazione della coppia di parole corrente ora ha il seguente aspetto: Text(appState.current.asLowerCase)
. Per trasformarla in qualcosa di più complesso, è buona norma estrarre questa riga in un widget separato. Avere widget separati per parti logiche distinte dell'interfaccia utente è un modo importante per gestire la complessità in Flutter.
Flutter fornisce un'utilità di refactoring per l'estrazione dei widget, ma prima di utilizzarla assicurati che la riga estratta acceda solo a ciò che è necessario. Al momento, la riga accede a appState
, ma in realtà deve solo sapere qual è la coppia di parole corrente.
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
.
Ora richiama il menu Ristruttura. In VS Code, puoi farlo in due modi:
- Fai clic con il tasto destro del mouse sul codice che vuoi sottoporre a refactoring (
Text
in questo caso) e seleziona Rifactoring… dal menu a discesa.
OPPURE
- Sposta il cursore sul codice del componente che vuoi sottoporre a refactoring (in questo caso
Text
) e premiCtrl+.
(Windows/Linux) oCmd+.
(Mac).
Nel menu Ristruttura, seleziona Estrai widget. Assegna un nome, ad esempio BigCard, e fai clic su Enter
.
Viene creato automaticamente un nuovo livello, BigCard
, alla fine del file corrente. La classe ha il seguente aspetto:
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);
}
}
// ...
Notare come l'app continui a funzionare anche durante questo refactoring.
Aggiunta di una carta di credito
Ora è il momento di trasformare questo nuovo widget nell'elemento di UI audace che abbiamo immaginato all'inizio di questa sezione.
Trova la classe BigCard
e il metodo build()
al suo interno. Come prima, richiama il menu Ristruttura nel widget Text
. Tuttavia, questa volta non dovrai estrarre il widget.
Seleziona invece A capo con spaziatura. Viene creato un nuovo widget principale attorno al widget Text
denominato Padding
. Dopo il salvataggio, vedrai che la parola casuale ha già più spazio.
Aumenta il valore predefinito di 8.0
. Ad esempio, utilizza qualcosa come 20
per una spaziatura interna più ampia.
Poi, vai a un livello superiore. Posiziona il cursore sul widget Padding
, apri il menu Ristruttura e seleziona Inserisci un a capo con un widget….
In questo modo puoi specificare il widget principale. Digita "Carta" e premi Invio.
Il widget Padding
e quindi anche Text
vengono racchiusi in un widget Card
.
Tema e stile
Per mettere in risalto la scheda, dipingila con un colore più intenso. Poiché è sempre buona norma mantenere una combinazione di colori coerente, utilizza l'opzione 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 svolgono un sacco di lavoro:
- Innanzitutto, il codice richiede il tema corrente dell'app con
Theme.of(context)
. - Il codice definisce quindi il colore della scheda come uguale a quello della proprietà
colorScheme
del tema. La combinazione di colori contiene molti colori eprimary
è il colore più evidente e che definisce l'app.
La scheda viene ora colorata 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 del seme per ColorScheme
.
Nota come il colore si anima in modo fluido. Questa è chiamata animazione implicita. Molti widget Flutter eseguono l'interpolazione tra i valori in modo che l'interfaccia utente non "salti" da uno stato all'altro.
Anche il pulsante in rilievo sotto la scheda cambia colore. Ecco la potenza dell'utilizzo di un Theme
a livello di app rispetto ai valori hardcoded.
TextTheme
La carta presenta ancora un problema: il testo è troppo piccolo e il 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),
),
);
}
// ...
Motivo del cambiamento:
- Se usi
theme.textTheme,
, accedi al tema dei caratteri dell'app. Questa classe include elementi comebodyMedium
(per testo standard di medie dimensioni),caption
(per le didascalie delle immagini) oheadlineLarge
(per titoli grandi). - La proprietà
displayMedium
è uno stile di grandi dimensioni pensato per il testo visualizzato. La parola display è usata qui nel senso tipografico, ad esempio in carattere tipografico display. La documentazione didisplayMedium
afferma che "gli stili di visualizzazione sono riservati a testo breve e importante", esattamente il nostro caso d'uso. - La proprietà
displayMedium
del tema potrebbe teoricamente esserenull
. Dart, il linguaggio di programmazione in cui stai scrivendo questa app, è sicuro per valori null, quindi non ti consente di chiamare metodi di oggetti potenzialmentenull
. In questo caso, però, puoi utilizzare l'operatore!
("operatore di esclamazione") per assicurarti che Dart sappia cosa stai facendo. (displayMedium
è sicuramente non nullo in questo caso. Il motivo per cui lo sappiamo esula dallo scopo di questo codelab. - La chiamata a
copyWith()
sudisplayMedium
restituisce una copia dello stile di testo con le modifiche che definisci. In questo caso, modifichi 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 per l'utilizzo su un colore principale dell'app.
L'app dovrebbe avere il seguente aspetto:
Se vuoi, puoi modificare ulteriormente la scheda. Ecco alcuni esempi:
copyWith()
ti consente di modificare molto di più dello stile del testo rispetto al semplice colore. Per visualizzare l'elenco completo delle proprietà che puoi modificare, posiziona il cursore in un punto qualsiasi tra le parentesi dicopyWith()
e premiCtrl+Shift+Space
(Windows/Linux) oCmd+Shift+Space
(Mac).- Analogamente, puoi modificare altri aspetti del 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 loro equivalentionPrimary
.
Migliorare l'accessibilità
Flutter rende le app accessibili per impostazione predefinita. Ad esempio, ogni app Flutter mostra correttamente tutti i testi e gli elementi interattivi dell'app agli screen reader come TalkBack e VoiceOver.
A volte, però, è necessario un po' di lavoro. Nel caso di questa app, lo screen reader potrebbe avere problemi a pronunciare alcune coppie di parole generate. Anche se le persone non hanno problemi a identificare le due parole in cheaphead, uno screen reader potrebbe pronunciare la ph al centro della 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 separate anziché di una parola composta assicura che gli screen reader le identifichino in modo appropriato e offre 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 sostituire i contenuti visivi del widget di testo con contenuti semantici più appropriati 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 questa funzionalità utilizzando uno screen reader sul tuo dispositivo.
Centrare l'interfaccia utente
Ora che la coppia di parole casuali è presentata con un'estetica sufficiente, è il momento di posizionarla al centro della finestra/schermo 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 sostituire 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'),
),
],
),
);
}
}
// ...
In questo modo, gli elementi secondari vengono centrati all'interno del Column
lungo l'asse principale (verticale).
I gruppi sono già centrati sull'asse trasversale della colonna (in altre parole, sono già centrati orizzontalmente). Tuttavia, il Column
in sé non è centrato all'interno del Scaffold
. Possiamo verificarlo utilizzando lo strumento di controllo dei widget.
Lo strumento di ispezione dei widget non rientra nell'ambito di questo codelab, ma puoi vedere che quando Column
è evidenziato, non occupa tutta la larghezza dell'app. Occupa solo lo spazio orizzontale necessario per i suoi elementi secondari.
Puoi semplicemente centrare la colonna stessa. Posiziona il cursore su Column
, richiama il menu Ristruttura (con Ctrl+.
o Cmd+.
) e seleziona A capo con centro.
L'app dovrebbe avere il seguente aspetto:
Se vuoi, puoi modificarlo ulteriormente.
- Puoi rimuovere il widget
Text
sopraBigCard
. Si potrebbe sostenere che il testo descrittivo ("Un'idea FANTASTICHERIA a caso:") non è più necessario perché l'interfaccia utente ha senso anche senza. E in questo modo è più pulito. - Puoi anche aggiungere un widget
SizedBox(height: 10)
traBigCard
eElevatedButton
. In questo modo, i due widget saranno un po' più distanziati. Il widgetSizedBox
occupa solo spazio e non esegue il rendering di nulla da solo. Viene spesso utilizzato per creare "spazi" visivi.
Con le modifiche facoltative, MyHomePage
contiene questo 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 alle preferite (o mettere "Mi piace") le parole generate.
6. Aggiungere funzionalità
L'app funziona e, a volte, fornisce anche coppie di parole interessanti. Tuttavia, ogni volta che l'utente fa clic su Avanti, ogni coppia di parole scompare definitivamente. Sarebbe meglio avere un modo per "ricordare" i suggerimenti migliori, ad esempio un pulsante "Mi piace".
Aggiungi la logica di business
Scorri fino a MyAppState
e aggiungi il seguente codice:
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
denominatafavorites
. Questa proprietà viene inizializzata con un elenco vuoto:[]
. - Hai anche specificato che l'elenco può contenere solo coppie di parole:
<WordPair>[]
, utilizzando i generici. In questo modo, la tua app sarà più solida: Dart rifiuta persino di eseguire l'app se provi ad aggiungere altro oltre aWordPair
. A tua volta, puoi utilizzare l'elencofavorites
sapendo che non possono esserci oggetti indesiderati (comenull
).
- 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 chiamanotifyListeners();
in un secondo momento.
Aggiungi il pulsante
Una volta risolta la "logica di business", è il momento di tornare a lavorare all'interfaccia utente. Per posizionare il pulsante "Mi piace" a sinistra del pulsante "Avanti" è necessario un Row
. Il widget Row
è l'equivalente orizzontale di Column
, che hai visto in precedenza.
Innanzitutto, inserisci il pulsante esistente in un Row
. Vai al metodo build()
di MyHomePage
, posiziona il cursore su ElevatedButton
, richiama il menu Ristruttura con Ctrl+.
o Cmd+.
e seleziona Inserisci a capo con riga.
Quando salvi, noterai che Row
si comporta in modo simile a Column
: per impostazione predefinita, raggruppa i suoi elementi secondari a sinistra. (Column
ha raggruppato i suoi figli in alto). Per risolvere il problema, puoi utilizzare lo stesso approccio di prima, ma con mainAxisAlignment
. Tuttavia, a fini didattici (di apprendimento), utilizza mainAxisSize
. In questo modo, Row
non occupa 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'),
),
],
),
],
),
),
);
}
}
// ...
L'interfaccia utente torna a quella precedente.
Aggiungi il pulsante Mi piace e collegalo a toggleFavorite()
. Per una sfida, prova prima a farlo da solo, senza guardare il blocco di codice di seguito.
Non è un problema se non lo fai esattamente come descritto di seguito. In realtà, non preoccuparti dell'icona del cuore, a meno che tu non voglia davvero una sfida impegnativa.
Inoltre, è del tutto normale fallire: dopotutto, è 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. Nella parte superiore del metodo build
, scegli l'icona appropriata a seconda che la coppia di parole corrente sia già tra i preferiti. Tieni inoltre presente l'utilizzo di SizedBox
per mantenere i due pulsanti un po' distanziati.
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 può vedere i preferiti. È arrivato il momento di aggiungere un'intera schermata separata alla nostra app. Ci vediamo nella prossima sezione.
7. Aggiungere la barra di navigazione laterale
La maggior parte delle app non riesce a visualizzare tutto in un'unica schermata. Probabilmente questa app in particolare potrebbe, ma a scopo didattico creerai una schermata separata per i preferiti dell'utente. Per passare da una schermata all'altra, implementerai il tuo primo StatefulWidget
.
Per arrivare al punto più importante di questo passaggio il prima possibile, suddividi MyHomePage
in due widget separati.
Seleziona tutto MyHomePage
, eliminalo e sostituiscilo 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 dell'interfaccia utente è pronta, ma non funziona. Se fai clic su ♥︎ (il cuore) nella barra di navigazione, non succede nulla.
Esamina le modifiche.
- Innanzitutto, tieni presente che l'intero contenuto di
MyHomePage
viene estratto in un nuovo widget,GeneratorPage
. L'unica parte del vecchio widgetMyHomePage
che non è stata estratta èScaffold
. - Il nuovo
MyHomePage
contiene unRow
con due elementi secondari. Il primo widget èSafeArea
e il secondo è un widgetExpanded
. SafeArea
garantisce che l'elemento secondario non sia oscurato da un notch hardware o da una barra di stato. In questa app, il widget si inserisce inNavigationRail
per evitare che i pulsanti di navigazione vengano oscurati da una barra di stato mobile, ad esempio.- Puoi modificare la riga
extended: false
in NavigationRail intrue
. Verranno visualizzate le etichette accanto alle icone. In un passaggio successivo, scoprirai come eseguire questa operazione automaticamente quando l'app dispone di spazio orizzontale sufficiente. - La barra di navigazione ha due destinazioni (Home e Preferiti), con le rispettive icone ed etichette. Inoltre, definisce il valore
selectedIndex
corrente. Un indice selezionato pari a zero seleziona la prima destinazione, un indice selezionato pari a uno seleziona la seconda destinazione e così via. Per il momento, è impostato su zero in modo predefinito. - 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 dell'indice richiesto conprint()
. - Il secondo elemento figlio di
Row
è il widgetExpanded
. I widget espansi sono estremamente utili in righe e colonne: ti consentono di creare layout in cui alcuni elementi secondari occupano solo lo spazio necessario (in questo casoSafeArea
) e altri widget devono occupare il maggior spazio possibile (in questo casoExpanded
). Un modo per definire i widgetExpanded
è che sono "avari". Per comprendere meglio il ruolo di questo widget, prova ad avvolgere il widgetSafeArea
con un altroExpanded
. Il layout risultante sarà simile al seguente:
- Due widget
Expanded
si dividono tutto lo spazio orizzontale disponibile, anche se la barra di navigazione aveva bisogno solo di un piccolo spazio a sinistra. - All'interno del widget
Expanded
è presente unContainer
colorato e all'interno del contenitore è presenteGeneratorPage
.
Widget stateless e stateful
Fino a questo momento, MyAppState
ha soddisfatto tutte le tue esigenze statali. Ecco perché tutti i widget che hai scritto finora sono senza stato. Non contengono stati mutabili propri. Nessun widget può modificarsi da solo, ma deve passare per MyAppState
.
Ma le cose stanno per cambiare.
Devi trovare un modo per mantenere il valore di selectedIndex
della barra di navigazione. Inoltre, vuoi poter modificare questo valore dal callback onDestinationSelected
.
Potresti aggiungere selectedIndex
come un'altra proprietà di MyAppState
. E funzionerebbe. Tuttavia, puoi immaginare che lo stato dell'app crescerebbe rapidamente oltre ogni limite se ogni widget memorizzasse i propri valori al suo interno.
Alcuni stati sono pertinenti solo per un singolo widget, quindi devono rimanere con quel widget.
Inserisci StatefulWidget
, un tipo di widget che ha State
. Innanzitutto, converti MyHomePage
in un widget con stato.
Posiziona il cursore sulla prima riga di MyHomePage
(quella che inizia con class MyHomePage...
) e richiama il menu Ristruttura utilizzando Ctrl+.
o Cmd+.
. Quindi seleziona Converti in StatefulWidget.
L'IDE crea un nuovo corso, _MyHomePageState
. Questa classe estende State
e può quindi gestire i propri valori. (Può cambiare se stessa). Inoltre, tieni presente che il metodo build
del vecchio widget senza stato è passato a _MyHomePageState
(anziché rimanere nel widget). È stato spostato esattamente così com'è, non è cambiato nulla all'interno del metodo build
. Ora vive semplicemente da qualche altra parte.
setState
Il nuovo widget stateful deve monitorare una sola 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 a0
. - Utilizza questa nuova variabile nella definizione di
NavigationRail
anziché il valore0
hardcoded presente finora. - Quando viene chiamato il callback
onDestinationSelected
, anziché stampare semplicemente il nuovo valore nella console, lo assegni aselectedIndex
all'interno di una chiamatasetState()
. Questa chiamata è simile al metodonotifyListeners()
utilizzato in precedenza: garantisce l'aggiornamento dell'interfaccia utente.
La barra di navigazione ora risponde all'interazione dell'utente. L'area espansa a destra rimane invariata. Questo accade perché il codice non utilizza selectedIndex
per determinare quale schermata viene visualizzata.
Utilizza selectedIndex
Inserisci il seguente codice nella parte superiore del metodo build
di _MyHomePageState
, appena 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 questo codice:
- Il codice dichiara una nuova variabile,
page
, di tipoWidget
. - Poi, un'istruzione switch assegna una schermata a
page
, in base al valore corrente inselectedIndex
. - Poiché non esiste ancora
FavoritesPage
, utilizzaPlaceholder
, un pratico widget che disegna un rettangolo barrato ovunque lo inserisci, contrassegnando quella parte dell'interfaccia utente come non completata.
- Applicando il principio di fail-fast, l'istruzione switch si assicura inoltre di generare un errore se
selectedIndex
non è 0 o 1. In questo modo, eviterai di riscontrare bug in futuro. Se aggiungi una nuova destinazione alla barra di navigazione e dimentichi di aggiornare questo codice, il programma si arresta in modo anomalo durante lo sviluppo (anziché lasciarti indovinare il motivo per cui le cose non funzionano o pubblicare un codice con bug in produzione).
Ora che page
contiene il widget che vuoi mostrare a destra, probabilmente puoi capire quale altra modifica è necessaria.
Ecco _MyHomePageState
dopo l'ultima 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.
),
),
],
),
);
}
}
// ...
Ora l'app passa dal nostro GeneratorPage
al segnaposto che a breve diventerà la pagina Preferiti.
Reattività
A questo punto, rendi adattabile la barra di navigazione. In altre parole, fai in modo che mostri automaticamente le etichette (utilizzando extended: true
) quando c'è spazio sufficiente.
Flutter fornisce diversi widget che ti aiutano a rendere le tue app automaticamente adattabili. Ad esempio, Wrap
è un widget simile a Row
o Column
che inserisce automaticamente i componenti secondari nella "riga" successiva (chiamata "run") quando non c'è spazio verticale o orizzontale sufficiente. C'è FittedBox
, un widget che adatta automaticamente i suoi elementi secondari allo spazio disponibile in base alle tue specifiche.
Tuttavia, NavigationRail
non mostra automaticamente le etichette quando lo spazio è sufficiente perché non può sapere quanto spazio è sufficiente in ogni contesto. Spetta a te, in qualità di sviluppatore, prendere questa decisione.
Supponiamo che tu decida di mostrare le etichette solo se MyHomePage
ha una larghezza di almeno 600 pixel.
In questo caso, il widget da utilizzare è LayoutBuilder
. Ti consente di modificare l'albero dei widget in base allo spazio disponibile.
Ancora una volta, utilizza il menu Rifactoring di Flutter in VS Code per apportare le modifiche necessarie. Questa volta, però, è un po' più complicato:
- All'interno del metodo
build
di_MyHomePageState
, posiziona il cursore suScaffold
. - Apri il menu Ristruttura con
Ctrl+.
(Windows/Linux) oCmd+.
(Mac). - Seleziona Inserisci un riquadro con il generatore di report e premi Invio.
- Modifica il nome del
Builder
appena aggiunto inLayoutBuilder
. - Modifica l'elenco dei parametri di callback da
(context)
a(context, constraints)
.
Il callback builder
di LayoutBuilder
viene chiamato ogni volta che cambiano le limitazioni. Ciò si verifica, ad esempio, quando:
- L'utente ridimensiona la finestra dell'app
- L'utente ruota lo smartphone dalla modalità Ritratto a quella Orizzontale o viceversa
- Alcuni widget accanto a
MyHomePage
aumentano di dimensioni, riducendo i vincoli diMyHomePage
- e così via.
Ora il codice può decidere se mostrare l'etichetta eseguendo una query sull'constraints
corrente. Apporta la seguente modifica di una 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 la tua app risponde al proprio ambiente, ad esempio dimensioni dello schermo, orientamento e piattaforma. In altre parole, è adattabile.
L'unica operazione che rimane da fare è sostituire Placeholder
con una schermata Preferiti effettiva. Questo aspetto è trattato nella sezione successiva.
8. Aggiungi una nuova pagina
Ricordi il widget Placeholder
che abbiamo utilizzato al posto della pagina Preferiti?
È ora di risolvere il problema.
Se ti senti in vena di avventure, prova a svolgere questo passaggio autonomamente. Il tuo obiettivo è mostrare l'elenco di favorites
in un nuovo widget senza stato, FavoritesPage
, e poi mostrare questo widget anziché Placeholder
.
Ecco alcuni suggerimenti:
- Se vuoi un
Column
che scorra, utilizza il widgetListView
. - Ricorda che puoi accedere all'istanza
MyAppState
da qualsiasi widget utilizzandocontext.watch<MyAppState>()
. - Se vuoi anche provare un nuovo widget,
ListTile
ha proprietà cometitle
(generalmente 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 i cicli
for
all'interno dei letterali di raccolta. Ad esempio, semessages
contiene un elenco di stringhe, puoi avere un codice come il seguente:
D'altra parte, se hai più dimestichezza con la programmazione funzionale, Dart ti consente anche di scrivere codice come messages.map((m) => Text(m)).toList()
. E, ovviamente, puoi sempre creare un elenco di widget e aggiungervi elementi in modo imperativo all'interno del metodo build
.
Il vantaggio di aggiungere la pagina Preferiti è che impari di più prendendo le tue decisioni. Lo svantaggio è che potresti riscontrare problemi che non sei ancora in grado di risolvere autonomamente. Ricorda: sbagliare è normale ed è uno degli elementi più importanti dell'apprendimento. Nessuno si aspetta che tu padroneggi lo sviluppo Flutter nella prima ora, e nemmeno tu dovresti.
Di seguito è riportato solo un modo per implementare la pagina dei preferiti. Il modo in cui è implementato ti incoraggerà (ci auguriamo) a giocare con il codice, a migliorare l'interfaccia utente e a renderla tua.
Ecco la nuova classe 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: Nessun preferito.*
- In caso contrario, viene visualizzato un elenco (scorrevole).
- L'elenco inizia con un riepilogo (ad esempio Hai 5 preferiti).
- Il codice esegue quindi un'iterazione di tutti i preferiti e crea un widget
ListTile
per ciascuno.
Ora non resta che sostituire il widget Placeholder
con un FavoritesPage
. E voilà!
Puoi ottenere il codice finale di questa app nel repository del codelab su GitHub.
9. Passaggi successivi
Complimenti!
Complimenti! Hai preso uno scafo non funzionale con un widget Column
e due Text
e ne hai creato una piccola app adattabile e piacevole.
Argomenti trattati
- Nozioni di base sul funzionamento di Flutter
- Creazione di layout in Flutter
- Collegamento delle interazioni degli utenti (ad esempio le pressioni dei pulsanti) al comportamento dell'app
- Organizzare il codice Flutter
- Rendere l'app adattabile
- Ottenere un aspetto coerente della tua app
E adesso?
- Fai altri esperimenti con l'app che hai scritto durante questo lab.
- Esamina il codice di questa versione avanzata della stessa app per scoprire come aggiungere elenchi animati, gradienti, transizioni sfumate e altro ancora.
- Per seguire il tuo percorso di apprendimento, visita la pagina flutter.dev/learn.