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 attuale ai preferiti e rivedere l'elenco dei nomi preferiti in una pagina separata. L'app è reattiva a schermi di dimensioni diverse.
Cosa imparerai
- 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
- Organizzare il codice Flutter
- Rendere la tua app adattabile (per schermi diversi)
- Ottenere un aspetto coerente della tua app
Inizierai con una struttura di base per passare subito alle parti interessanti.
Ed ecco Filip che ti guida attraverso l'intero codelab.
Fai clic su Avanti per iniziare il lab.
2. Configurare l'ambiente Flutter
Editor
Per rendere questo codelab il più semplice possibile, presupponiamo che utilizzerai Visual Studio Code (VS Code) come ambiente di sviluppo. È senza costi e funziona su tutte le principali piattaforme.
Naturalmente puoi utilizzare l'editor che preferisci: Android Studio, altri IDE IntelliJ, Emacs, Vim o Notepad++. Funzionano tutti con Flutter.
Ti consigliamo di utilizzare VS Code per questo codelab perché le istruzioni utilizzano scorciatoie specifiche di VS Code. È più facile dire cose come "fai clic qui" o "premi questo tasto" anziché "esegui l'azione appropriata nell'editor per fare X".
Scegliere un target 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 unico sistema operativo per cui sviluppare principalmente. Questo è il "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. Tuttavia, puoi anche scegliere Windows come target di sviluppo, il che significa che l'app in fase di sviluppo viene eseguita come app 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 rapido con stato. Flutter non può eseguire il ricaricamento rapido delle applicazioni web.
Fai subito la tua scelta. Ricorda: puoi sempre eseguire la tua app su altri sistemi operativi in un secondo momento. Il fatto è che avere un obiettivo di sviluppo chiaro in mente rende il passaggio successivo più semplice.
Installare 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 correlati alla destinazione 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 Windows o Xcode per macOS)
Nella sezione successiva creerai il tuo primo progetto Flutter.
Se finora hai riscontrato problemi, potresti trovare utili alcune di queste domande e risposte (di StackOverflow) per la risoluzione dei problemi.
Domande frequenti
- Come faccio a trovare il percorso dell'SDK Flutter?
- Cosa faccio quando il comando Flutter non viene trovato?
- Come faccio a risolvere il problema "In attesa che un altro comando Flutter rilasci il blocco di avvio"?
- Come faccio a indicare a Flutter la posizione dell'installazione dell'SDK Android?
- Come faccio a risolvere l'errore Java durante l'esecuzione di
flutter doctor --android-licenses
? - Come faccio a risolvere il problema relativo allo strumento Android
sdkmanager
non trovato? - Come faccio a risolvere l'errore "Il componente
cmdline-tools
non è presente"? - 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
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.
Successivamente, seleziona Applicazione e poi una cartella in cui creare il progetto. Potrebbe essere la tua home directory o qualcosa come C:\src\
.
Infine, assegna un nome al progetto. Qualcosa come namer_app
o my_awesome_namer
.
Flutter ora crea la cartella del progetto e VS Code la apre.
Ora sovrascrivi i contenuti di tre file con una struttura di base dell'app.
Copiare e incollare l'app iniziale
Nel riquadro a sinistra di VS Code, assicurati che sia selezionato Explorer e apri il file pubspec.yaml
.
Sostituisci i contenuti di questo file con i seguenti:
pubspec.yaml
name: namer_app
description: "A new Flutter project."
publish_to: "none"
version: 0.1.0
environment:
sdk: ^3.9.0
dependencies:
flutter:
sdk: flutter
english_words: ^4.0.0
provider: ^6.1.5
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
flutter:
uses-material-design: true
Il file pubspec.yaml
specifica le informazioni di base sulla tua app, come la versione attuale, le dipendenze e gli asset con cui verrà distribuita.
Poi, apri un altro file di configurazione nel progetto, analysis_options.yaml
.
Sostituisci 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 il livello di rigore che Flutter deve applicare durante l'analisi del codice. Poiché è la tua prima incursione in Flutter, stai dicendo all'analizzatore di fare con calma. Puoi sempre modificarla in un secondo momento. Infatti, man mano che ti avvicini alla pubblicazione di un'app di produzione effettiva, vorrai quasi certamente rendere l'analizzatore più rigoroso.
Infine, apri il file main.dart
nella directory lib/
.
Sostituisci i contenuti di questo file con i seguenti:
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(
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 coppia 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 modificarlo.
Mentre lib/main.dart
è aperto, individua il pulsante "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. Per ora non sembra molto:
First Hot Reload
In fondo a 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 la stessa. Questo è il famoso Hot Reload stateful di Flutter in azione. Il ricaricamento rapido viene attivato quando salvi le modifiche a un file di origine.
Domande frequenti
- Cosa succede se Hot Reload non funziona in VSCode?
- Devo premere "r" per l'hot reload in VSCode?
- Hot Reload funziona sul web?
- Come faccio a rimuovere il banner "Debug"?
Aggiungere un pulsante
Poi aggiungi un pulsante in fondo al Column
, subito 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 viene aggiornata di nuovo: viene visualizzato un pulsante e, quando lo clicchi, la console di debug in VS Code mostra il messaggio button pressed! (pulsante premuto).
Un corso intensivo su Flutter in 5 minuti
Per quanto sia divertente guardare la console di debug, vuoi che il pulsante faccia 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, indica a Flutter di eseguire solo 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(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
// ...
La classe MyApp
estende StatelessWidget
. I widget sono gli elementi con 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 (ne parleremo più avanti), assegna un nome all'app, definisce il tema visivo e imposta il widget "Home", il punto di partenza dell'app.
lib/main.dart
// ...
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
// ...
Successivamente, la classe MyAppState
definisce lo stato dell'app. Questa è la tua prima incursione in Flutter, quindi questo codelab sarà semplice e mirato. 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 necessari per il funzionamento dell'app. Al momento contiene una sola variabile con la coppia di parole casuali corrente. Aggiungerai altri dettagli in un secondo momento.- La classe di stato estende
ChangeNotifier
, il che significa che può notificare ad altri i propri cambiamenti. 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ò accedere allo 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 numero di riga nel codice riportato sopra:
- Ogni widget definisce un metodo
build()
che viene chiamato automaticamente ogni volta che cambiano le circostanze del widget, in modo che sia sempre aggiornato. MyHomePage
tiene traccia delle modifiche allo stato attuale dell'app utilizzando il metodowatch
.- Ogni metodo
build
deve restituire un widget o (più comunemente) un albero di widget nidificati. In questo caso, il widget di primo livello èScaffold
. Non lavorerai conScaffold
in questo codelab, ma è un widget utile e si trova nella stragrande maggioranza delle app Flutter del mondo reale. Column
è uno dei widget di layout più basilari di Flutter. Prende un numero qualsiasi di figli e li inserisce in una colonna dall'alto verso il basso. Per impostazione predefinita, la colonna posiziona visivamente i suoi elementi secondari in alto. A breve 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
fornisce diversi metodi getter utili, comeasPascalCase
oasSnakeCase
. Qui utilizziamoasLowerCase
, ma puoi modificarlo ora se preferisci una delle alternative. - Nota come il codice Flutter utilizzi molto le virgole finali. Questa virgola non è necessaria perché
children
è l'ultimo (e anche l'unico) membro di questo elenco di parametriColumn
. Tuttavia, in genere è una buona idea utilizzare le virgole finali: rendono banale l'aggiunta di altri membri e fungono anche da suggerimento per il formattatore automatico di Dart per inserire un carattere di nuova riga. Per ulteriori informazioni, vedi Formattazione del codice.
A questo punto, collega 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 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 l'app ora. 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
Ecco l'aspetto attuale dell'app.
Non ideale. La parte centrale dell'app, ovvero la coppia di parole generata in modo casuale, dovrebbe essere più visibile. Dopotutto, è il motivo principale per cui i nostri utenti utilizzano questa app. Inoltre, i contenuti dell'app sono stranamente decentrati e l'intera app è in bianco e nero.
Questa sezione affronta questi problemi lavorando sulla progettazione dell'app. L'obiettivo finale di questa sezione è simile al seguente:
Estrarre un widget
La riga responsabile della visualizzazione della coppia di parole corrente ora ha il seguente aspetto: Text(appState.current.asLowerCase)
. Per trasformarlo in qualcosa di più complesso, è consigliabile estrarre questa riga in un widget separato. Avere widget separati per parti logiche separate della UI è un modo importante per gestire la complessità in Flutter.
Flutter fornisce un helper di refactoring per l'estrazione dei widget, ma prima di utilizzarlo, assicurati che la riga estratta acceda solo a ciò che le serve. 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 Refactor. In VS Code, puoi farlo in due modi:
- Fai clic con il tasto destro del mouse sul frammento di codice che vuoi refactoring (
Text
in questo caso) e seleziona Refactoring… dal menu a discesa.
OPPURE
- Sposta il cursore sul codice del componente che vuoi refactoring (
Text
, in questo caso) e premiCtrl+.
(Windows/Linux) oCmd+.
(Mac).
Nel menu Refactor, seleziona Estrai widget. Assegna un nome, ad esempio BigCard, e fai clic su Enter
.
In questo modo viene creato automaticamente un nuovo corso, BigCard
, alla fine del file corrente. La classe ha un aspetto simile al seguente:
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 continui a funzionare anche durante questo refactoring.
Aggiunta di una carta di credito
Ora è il momento di trasformare questo nuovo widget nel componente della UI in evidenza che avevamo immaginato all'inizio di questa sezione.
Trova la classe BigCard
e il metodo build()
al suo interno. Come prima, richiama il menu Refactor nel widget Text
. Tuttavia, questa volta non estrarrai il widget.
Seleziona invece A capo con spaziatura interna. Viene creato un nuovo widget principale intorno al widget Text
denominato Padding
. Dopo il salvataggio, noterai che la parola casuale ha già più spazio.
Aumenta il padding rispetto al valore predefinito di 8.0
. Ad esempio, utilizza un valore come 20
per una spaziatura interna più ampia.
Poi, sali di un livello. Posiziona il cursore sul widget Padding
, visualizza il menu Refactor e seleziona Wrap with widget….
In questo modo puoi specificare il widget principale. Digita "Scheda" e premi Invio.
In questo modo, il widget Padding
e quindi anche Text
vengono racchiusi in un widget Card
.
lib/main.dart
// ...
class BigCard extends StatelessWidget {
const BigCard({super.key, required this.pair});
final WordPair pair;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(pair.asLowerCase),
),
);
}
}
// ...
L'app ora avrà un aspetto simile a questo:
Tema e stile
Per far risaltare di più la scheda, colorala con un colore più intenso. Poiché è sempre una buona idea 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)
. - Il codice definisce quindi il colore della scheda in modo che sia uguale alla proprietà
colorScheme
del tema. La combinazione di colori contiene molti colori eprimary
è il colore più evidente e caratteristico dell'app.
Ora la scheda è 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 principale per ColorScheme
.
Nota come il colore si anima in modo fluido. Questo tipo di animazione è chiamato animazione implicita. Molti widget Flutter interpolano in modo fluido i valori in modo che l'interfaccia utente non "salti" tra gli stati.
Anche il pulsante in rilievo sotto la scheda cambia colore. Questo è il vantaggio di utilizzare un Theme
a livello di app anziché codificare i valori.
TextTheme
La carta presenta ancora un problema: il testo è troppo piccolo e il colore è difficile da leggere. Per risolvere il problema, apportate 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 di questa modifica:
- Utilizzando
theme.textTheme,
, accedi al tema dei caratteri dell'app. Questa classe include membri comebodyMedium
(per testo standard di medie dimensioni),caption
(per le didascalie delle immagini) oheadlineLarge
(per i titoli grandi). - La proprietà
displayMedium
è uno stile grande pensato per il testo visualizzato. La parola display è usata qui in senso tipografico, come in carattere di visualizzazione. La documentazione perdisplayMedium
afferma che "gli stili di visualizzazione sono riservati a testi brevi e importanti", 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, è null-safe, quindi non ti consente di chiamare metodi di oggetti potenzialmentenull
. In questo caso, però, puoi utilizzare l'operatore!
("operatore bang") per assicurare a Dart che sai cosa stai facendo.displayMedium
non è sicuramente null in questo caso. Il motivo per cui lo sappiamo non rientra nell'ambito di questo codelab. - La chiamata
copyWith()
sudisplayMedium
restituisce una copia dello stile di testo con le modifiche che definisci. In questo caso, stai modificando solo il colore del testo. - Per ottenere il nuovo colore, devi accedere di nuovo al tema dell'app. La proprietà
onPrimary
della combinazione di colori definisce un colore adatto all'utilizzo sul colore principale dell'app.
L'app dovrebbe ora avere un aspetto simile al seguente:
Se vuoi, puoi modificare ulteriormente la carta. Ecco alcuni esempi:
copyWith()
ti consente di modificare molti altri aspetti dello stile del testo oltre al colore. Per visualizzare l'elenco completo delle proprietà che puoi modificare, posiziona il cursore all'interno delle parentesi dicopyWith()
e premiCtrl+Shift+Space
(Windows/Linux) oCmd+Shift+Space
(Mac).- Allo stesso modo, 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 tutto il testo 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. Mentre gli esseri umani non hanno problemi a identificare le due parole in cheaphead, uno screen reader potrebbe pronunciare ph al centro della parola come f.
Una soluzione è sostituire pair.asLowerCase
con "${pair.first} ${pair.second}"
. Quest'ultima 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 correttamente e offre un'esperienza migliore agli utenti con disabilità visiva.
Tuttavia, potresti voler 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ù adatti agli 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 tocco visivo sufficiente, è il momento di posizionarla al centro della finestra/dello 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 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 l'asse principale (verticale).
I figli sono già centrati lungo l'asse cross della colonna (in altre parole, sono già centrati orizzontalmente). Ma il Column
stesso non è centrato all'interno del Scaffold
. Possiamo verificarlo utilizzando lo strumento di controllo dei widget.
L'ispettore widget non rientra nell'ambito di questo codelab, ma puoi notare che quando Column
è evidenziato, non occupa l'intera larghezza dell'app. Occupa solo lo spazio orizzontale necessario ai suoi elementi secondari.
Puoi centrare la colonna stessa. Posiziona il cursore su Column
, visualizza il menu Refactor (con Ctrl+.
o Cmd+.
) e seleziona Wrap with Center.
L'app dovrebbe ora avere un aspetto simile al seguente:
Se vuoi, puoi perfezionare ulteriormente questo aspetto.
- Puoi rimuovere il widget
Text
sopraBigCard
. Si potrebbe sostenere che il testo descrittivo ("Un'idea AWESOME a caso:") non sia più necessario, in quanto la UI ha senso anche senza. In questo modo è più pulito. - Puoi anche aggiungere un widget
SizedBox(height: 10)
traBigCard
eElevatedButton
. In questo modo, i due widget sono un po' più distanti tra loro. Il widgetSizedBox
occupa solo spazio e non esegue il rendering di nulla. 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 ai preferiti (o "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 per sempre. Sarebbe meglio avere un modo per "ricordare" i suggerimenti migliori, ad esempio un pulsante "Mi piace".
Aggiungere 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
chiamatafavorites
. Questa proprietà viene inizializzata con un elenco vuoto:[]
. - Hai anche specificato che l'elenco può contenere solo coppie di parole:
<WordPair>[]
, utilizzando generici. In questo modo la tua app è più solida: Dart si rifiuta persino di eseguirla se provi ad aggiungere qualsiasi altro valore diverso daWordPair
. A sua volta, puoi utilizzare l'elencofavorites
sapendo che non ci saranno mai oggetti indesiderati (comenull
) nascosti.
- 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 seguito.
Aggiungere il pulsante
Ora che la "logica di business" è stata definita, è il momento di lavorare di nuovo sull'interfaccia utente. Il posizionamento del pulsante "Mi piace" a sinistra del pulsante "Avanti" richiede un Row
. Il widget Row
è l'equivalente orizzontale di Column
, che hai visto in precedenza.
Per prima cosa, racchiudi il pulsante esistente in un Row
. Vai al metodo MyHomePage
di build()
, posiziona il cursore su ElevatedButton
, richiama il menu Refactor con Ctrl+.
o Cmd+.
e seleziona Wrap with Row.
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 cima.) Per risolvere il problema, puoi utilizzare lo stesso approccio di prima, ma con mainAxisAlignment
. Tuttavia, a fini didattici (di apprendimento), utilizza mainAxisSize
. 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'),
),
],
),
],
),
),
);
}
}
// ...
L'interfaccia utente è tornata alla versione precedente.
Poi aggiungi il pulsante Mi piace e collegalo a toggleFavorite()
. Per una sfida, prova prima a farlo da solo, senza guardare il blocco di codice riportato di seguito.
Non preoccuparti se non lo fai esattamente come descritto di seguito. Infatti, non preoccuparti dell'icona del cuore, a meno che tu non voglia una sfida davvero impegnativa.
È anche normale commettere errori, dato che è 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à nei preferiti. Inoltre, nota l'utilizzo di SizedBox
per tenere i due pulsanti un po' distanti.
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. È il momento di aggiungere una schermata completamente separata alla nostra app. Ci vediamo nella prossima sezione.
7. Aggiungere la modalità di navigazione laterale
La maggior parte delle app non può contenere tutto in un'unica schermata. Questa app in particolare probabilmente 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 nocciolo di questo passaggio il prima possibile, dividi MyHomePage
in due widget separati.
Seleziona tutto il codice MyHomePage
, eliminalo e sostituiscilo con il seguente:
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, nota 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 bambini. Il primo widget èSafeArea
, il secondo è un widgetExpanded
. SafeArea
assicura che il relativo elemento secondario non sia oscurato da una tacca hardware o da una barra di stato. In questa app, il widget si estende fino aNavigationRail
per evitare che i pulsanti di navigazione vengano oscurati, ad esempio, da una barra di stato mobile.- Puoi modificare la riga
extended: false
inNavigationRail
intrue
. In questo modo, le etichette vengono visualizzate accanto alle icone. In un passaggio futuro, imparerai a farlo automaticamente quando l'app ha spazio orizzontale sufficiente. - La barra di navigazione ha due destinazioni (Home e Preferiti), con le rispettive icone ed etichette. Definisce anche l'
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 ora, è codificato 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 dell'indice richiesto conprint()
. - Il secondo elemento figlio di
Row
è il widgetExpanded
. I widget espansi sono estremamente utili nelle righe e nelle colonne: ti consentono di esprimere layout in cui alcuni elementi secondari occupano solo lo spazio necessario (SafeArea
, in questo caso) e altri widget devono occupare il maggior spazio possibile (Expanded
, in questo caso). Un modo per pensare ai widgetExpanded
è che sono "avidi". Se vuoi comprendere meglio il ruolo di questo widget, prova a racchiudere il widgetSafeArea
con un altro widgetExpanded
. Il layout risultante sarà simile a questo:
- Due widget
Expanded
si dividono tutto lo spazio orizzontale disponibile, anche se la barra di navigazione aveva bisogno solo di una piccola porzione a sinistra. - All'interno del widget
Expanded
, c'è unContainer
colorato e all'interno del contenitore, ilGeneratorPage
.
Widget stateless e stateful
Finora, MyAppState
ha coperto tutte le tue esigenze statali. Per questo motivo, tutti i widget che hai scritto finora sono senza stato. Non contengono alcuno stato modificabile proprio. Nessuno dei widget può modificarsi autonomamente, ma deve passare attraverso 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. Ma puoi immaginare che lo stato dell'app crescerebbe rapidamente oltre ogni limite se ogni widget memorizzasse i propri valori.
Alcuni stati sono pertinenti solo per un singolo widget, quindi devono rimanere con quel widget.
Inserisci il StatefulWidget
, un tipo di widget che ha State
. Innanzitutto, converti MyHomePage
in un widget stateful.
Posiziona il cursore sulla prima riga di MyHomePage
(quella che inizia con class MyHomePage...
) e visualizza il menu Refactor utilizzando Ctrl+.
o Cmd+.
. Poi seleziona Converti in StatefulWidget.
L'IDE crea una nuova classe per te, _MyHomePageState
. Questa classe estende State
e può quindi gestire i propri valori. (Può cambiare da solo.) Tieni presente inoltre che il metodo build
del vecchio widget stateless è stato spostato in _MyHomePageState
(anziché rimanere nel widget). È stato spostato letteralmente: non è cambiato nulla all'interno del metodo build
. Ora si trova semplicemente altrove.
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 inizializzala su0
. - Utilizzi questa nuova variabile nella definizione di
NavigationRail
anziché il valore0
codificato in modo permanente che era presente fino ad ora. - 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: assicura che la UI venga aggiornata.
La barra di navigazione ora risponde all'interazione dell'utente. ma l'area espansa a destra rimane invariata. Questo perché il codice non utilizza selectedIndex
per determinare quale schermata viene visualizzata.
Utilizza selectedIndex
Inserisci il seguente codice all'inizio 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 widget pratico che disegna un rettangolo incrociato ovunque lo posizioni, contrassegnando quella parte dell'interfaccia utente come non completata.
- Applicando il principio fail-fast, l'istruzione switch si assicura anche di generare un errore se
selectedIndex
non è 0 o 1. In questo modo, si evitano 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 (invece di farti indovinare perché le cose non funzionano o di farti pubblicare un codice pieno di bug in produzione).
Ora che page
contiene il widget che vuoi mostrare a destra, probabilmente puoi immaginare quale altro cambiamento è necessario.
Ecco _MyHomePageState
dopo l'unica 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 pagina GeneratorPage
al segnaposto che diventerà presto la pagina Preferiti.
Reattività
A questo punto, rendi la barra di navigazione adattabile. ovvero, far sì che le etichette vengano visualizzate automaticamente (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 esegue automaticamente il wrapping dei pannelli secondari alla "riga" successiva (chiamata "run") quando non c'è spazio verticale o orizzontale sufficiente. Esiste FittedBox
, un widget che inserisce automaticamente il figlio nello spazio disponibile in base alle tue specifiche.
Tuttavia, NavigationRail
non mostra automaticamente le etichette quando lo spazio è sufficiente perché non può sapere cosa è uno 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.
Il widget da utilizzare, in questo caso, è LayoutBuilder
. Ti consente di modificare la struttura dei widget in base alla quantità di spazio disponibile.
Ancora una volta, utilizza il menu Refactor 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
. - Richiama il menu Refactor con
Ctrl+.
(Windows/Linux) oCmd+.
(Mac). - Seleziona Wrap with Builder (Esegui il wrapping con Builder) e premi Invio.
- Modifica il nome di
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 i vincoli cambiano. Ciò si verifica, ad esempio, quando:
- L'utente ridimensiona la finestra dell'app
- L'utente ruota lo smartphone dalla modalità verticale a quella orizzontale o viceversa
- Alcuni widget accanto a
MyHomePage
aumentano di dimensioni, riducendo i vincoli diMyHomePage
Ora il tuo codice può decidere se mostrare l'etichetta eseguendo una query sul constraints
corrente. Apporta la seguente modifica su 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 la tua app risponde al suo ambiente, ad esempio alle dimensioni dello schermo, all'orientamento e alla piattaforma. In altre parole, è reattivo.
L'unica cosa che rimane da fare è sostituire Placeholder
con una schermata Preferiti effettiva. Questo argomento è 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 avventuroso, prova a eseguire questo passaggio da solo. Il tuo obiettivo è mostrare l'elenco di favorites
in un nuovo widget stateless, FavoritesPage
, e poi mostrare questo widget anziché Placeholder
.
Ecco alcuni suggerimenti:
- Se vuoi un
Column
scorrevole, utilizza il widgetListView
. - Ricorda che puoi accedere all'istanza
MyAppState
da qualsiasi widget utilizzandocontext.watch<MyAppState>()
. - Se vuoi provare anche 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 loop
for
all'interno dei valori letterali delle raccolte. Ad esempio, semessages
contiene un elenco di stringhe, puoi avere un codice come il seguente:
D'altra parte, se hai più familiarità con la programmazione funzionale, Dart ti consente anche di scrivere codice come messages.map((m) => Text(m)).toList()
. E, naturalmente, puoi sempre creare un elenco di widget e aggiungervi elementi in modo imperativo all'interno del metodo build
.
Il vantaggio di aggiungere personalmente 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: fallire va bene ed è uno degli elementi più importanti dell'apprendimento. Nessuno si aspetta che tu riesca a sviluppare con Flutter alla prima ora, e nemmeno tu dovresti.
Quello che segue è solo un modo per implementare la pagina dei preferiti. Il modo in cui è implementato ti (si spera) ispirerà a giocare con il codice, migliorare la UI e personalizzarla.
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: Ancora nessun preferito.
- In caso contrario, viene visualizzato un elenco (scorribile).
- L'elenco inizia con un riepilogo (ad esempio, Hai 5 preferiti).
- Il codice scorre quindi tutti i preferiti e crea un widget
ListTile
per ciascuno.
Ora non resta che sostituire il widget Placeholder
con un widget FavoritesPage
. Et voilà!
Puoi ottenere il codice finale di questa app nel repository del codelab su GitHub.
9. Passaggi successivi
Complimenti!
Guarda un po' chi c'è! Hai preso un'impalcatura non funzionante con un Column
e due widget Text
e l'hai trasformata in una piccola app reattiva e deliziosa.
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
- Organizzare il codice Flutter
- Rendere reattiva l'app
- Ottenere un aspetto coerente della tua app
E adesso?
- Sperimenta di più con l'app che hai scritto durante questo lab.
- Dai un'occhiata al codice di questa versione avanzata della stessa app per scoprire come aggiungere elenchi animati, sfumature, dissolvenze incrociate e altro ancora.
- Segui il tuo percorso di apprendimento visitando la pagina flutter.dev/learn.