Uno degli aspetti basilari nella creazione di un’applicazione mobile riguarda sicuramente la gestione degli assets (comunemente chiamate risorse) e delle immagini come la foto del profilo utente, foto provenienti dal web, o più semplicemente le icone di determinate azioni che l’utente può compiere.
In questa lezione focalizzeremo la nostra attenzione proprio sulla creazione degli asset e su come gestire le immagini caricate localmente nella nostra app.
La cartella assets e il pubspec.yaml
Un asset altro non è se non un file che viene rilasciato e distribuito con l’app ed è accessibile a runtime da quest’ultima per mostrare all’utente un’informazione.
I tipi comuni di risorse che possiamo trovare in un asset includono dati statici (ad esempio i file JSON), file di configurazione, font, icone e immagini tra i quali ritroviamo diversi formati, come ad esempio:
- JPEG;
- WebP;
- PNG;
- BMP;
- GIF / GIF animate.
Differentemente da quanto si possa pensare, aggiungere gli asset ad un’app Flutter non è una prassi molto complessa.
Posizionandoci nella root del nostro progetto, aggiungiamo una nuova cartella come segue.
Tasto destro sul progetto -> New -> Directory
Nella finestra che comparirà inseriamo come nome della cartella la parola assets. Poiché questa cartella può contenere diverse tipologie di dato, è utile suddividere la cartella assets in sottocartelle. Analogamente a quanto appena fatto, aggiungiamo la cartella images e al suo interno carichiamo alcune immagini di esempio che potete trovare al seguente link.
Per permettere a Flutter di identificare ed utilizzare la cartella assets è necessario modificare il file pubspec.yaml definito a livello della root del progetto modificando la sezione flutter
come segue.
flutter:
assets:
- assets/images/
In questo modo Flutter identificherà il percorso assets/images/ come punto da cui recuperare tutte le immagini presenti in quel percorso. Nel dettaglio, durante la compilazione dell’app, Flutter collocherà questa cartella di assets in un archivio speciale chiamato asset bundle, a cui l'app accede durante l'esecuzione.
Qualora volessimo dare visibilità solo ad uno specifico file, basterà specificare il percorso a quel file. Ad esempio, immaginiamo che la nostra applicazione debba accedere solo a due immagini che si chiamano dog1.jpg e dog2.jpg. In questo caso, modificheremo la precedente dichiarazione come segue.
flutter:
assets:
- assets/images/dog1.jpg
- assets/images/dog2.jpg
L’ordine con cui vengono dichiarati i percorsi non influenza la loro inclusione all’interno dell’assets bundle generato da Flutter.
Il widget Image
Per la visualizzazione di immagini e icone, Flutter offre il widget Image
. Questo Widget
mette a disposizione del programmatore diversi costruttori che possono essere utilizzati per inizializzarlo.
Costruttore | Descrizione |
---|---|
Image |
l’immagine è caricata tramite un ImageProvider |
Image.asset |
l’immagine viene caricata attraverso l’AssetBundle utilizzando una chiave che identifica univocamente la risorsa. In particolare, l’AssetBundle definisce le risorse come immagini e stringhe che possono essere utilizzate dall’app e sono state aggiunte in fase di compilazione nell’asset bundle |
Image.network |
carica un’immagine disponibile in rete tramite la sua URL |
Image.file |
l’immagine viene creata a partire da un oggetto File |
Image.memory |
l’immagine viene caricata dalla memoria tramite l’utilizzo della classe Uint8List che definisce una lista di dimensioni predefinite a interi unsigned a 8-bit |
Le proprietà
Come tutti i widget che abbiamo visto in queste lezioni, il widget Image fornisce un insieme di proprietà che, se definite, ne modificano il comportamento caratterizzandone il layout. Tra queste ritroviamo:
Proprietà | Tipo Accettato | Descrizione |
---|---|---|
alignment |
AlignmentGeometry |
definisce l’allineamento dell’immagine rispetto al nodo padre attraverso la definizione di un widget di tipo AlignmentGeometry come Alignment o AlignmentDirectional |
color |
Color |
se definito, il colore scelto viene combinato con ogni pixel dell’immagine in base alla definizione della proprietà colorBlendMode |
colorBlendMode |
BlendMode |
Questa proprietà definisce la modalità in cui applicare il colore scelto con la proprietà color ai pixel dell’immagine. Esistono diverse modalità che possono essere utilizzate, ognuna delle quali definisce un algoritmo che prende in ingresso due input:
Si può pensare alla destination come ad un background. Tra i diversi algoritmi, quello di default è srcIn. Si rimanda alla documentazione per maggiori informazioni. |
filterQuality |
FilterQuality |
permette di definire il livello di qualità dell’immagine utilizzando le costanti definite da FilterQuality ognuna delle quali implementa una determinata funzione di interpolazione. Di default è impostata su FilterQuality.low |
fit |
BoxFit |
indica come iscrivere l'immagine nello spazio assegnato durante il layout mediante la definizione di una delle proprietà offerte da BoxFit |
height |
double |
definisce l’altezza dell’immagine |
image |
ImageProvider |
l’immagine da mostrare utilizzando un ImageProvider invece dei costruttori come Image.network |
repeat |
ImageRepeat |
definisce la modalità con cui riempire la porzione di layout non coperta dall’immagine. L’immagine caricata verrà ripetuta verticalmente e/o orizzontalmente utilizzando le proprietà della classe ImageRepeat . Il valore di default è ImageRepeat.noRepeat |
semanticLabel |
String |
permette di definire una descrizione testuale per l’immagine per essere utilizzata da TalkBack di Android e VoiceOver di iOS |
width |
double |
definisce la larghezza dell’immagine |
Compreso, quindi, quali sono le principali proprietà offerte dal widget Image
, vediamo adesso come utilizzarle con qualche esempio pratico.
Esempi pratici
Per iniziare creiamo un nuovo progetto come mostrato nella lezione 6 di questa guida e lasciando inalterati gli import
, il metodo main()
e la classe MyApp
, cancelliamo il resto per aggiungere il seguente StatelessWidget
composto da uno Scaffold
per la definizione dell’AppBar
e di una SingleChildScrollView
(di cui abbiamo già parlato nella precedente lezione) che conterrà tutti i nostri esempi pratici di caricamento delle immagini.
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Lesson 12 - Assets and Images'),
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
//we will add our widgets here.
],
)),
);
}
}
Modifichiamo infine la classe MyApp
per impostare il widget appena creato come home.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// . . .
home: MyPage(),
);
}
}
Ora siamo pronti per le nostre sperimentazioni.
Definiamo un insieme di costanti private che rappresentano il percorso alla cartella assets definita nel pubspec.yml e il percorso di ogni singola immagine.
const _PATH = "assets/images";
const _PIC01 = "$_PATH/sunset.jpg";
const _PIC02 = "$_PATH/dog1.jpg";
const _PIC03 = "$_PATH/dog2.jpg";
const _PIC04 = "$_PATH/fox.jpg";
const _PIC05 = "$_PATH/sping.jpg";
Questo passo renderà la scrittura del widget più compatta e intuitiva.
Creiamo adesso il nostro primo Image
widget utilizzando il costruttore Image.asset
che richiede in input il percorso al file come segue.
children: <Widget>[
Container(
constraints: BoxConstraints.expand(height: 300),
alignment: Alignment.center,
child: Image.asset(
_PIC02,
fit: BoxFit.cover,
),
)
]
In questo modo abbiamo caricato dagli asset
una nuova immagine posizionata al centro del nostro Container
, specificando di caricare l’immagine _PIC02
e di impostare la proprietà fit
in modalità cover
, ossia rendendo l’immagine più piccola possibile ma riempendo allo stesso tempo il container.
Eseguendo l’applicazione otterremo il seguente risultato
Figura 64. Caricamento di un’immagine tramite il costruttore asset per a) Android e b) iOS (click per ingrandire)
Analogamente a quanto appena fatto possiamo caricare un’immagine dagli asset usando l’AssetImage
, che è un ImageProvider
a tutti gli effetti. Creiamo quindi un nuovo container definendo il widget Image come segue.
children: <Widget>[
// . . .
Container(
constraints: BoxConstraints.expand(height: 300),
alignment: Alignment.center,
child: Image(
image: AssetImage(_PIC05),
fit: BoxFit.cover,
),
)
]
In questo modo non è necessario utilizzare il costruttore in quanto sfruttiamo la proprietà image per istanziare un nuovo AssetImage
. Eseguendo l’applicazione, otterremo il seguente risultato.
Figura 65. Caricamento di un’immagine tramite AssetImage per a) Android e b) iOS (click per ingrandire)
Come si può notare sia che utilizziamo il costruttore Image.asset
sia che utilizziamo l'AssetImage
otterremo lo stesso risultato, ossia il caricamento dell'imamgine.Per semplicità, utilizzeremo nei prossimi esempi il costruttore Image.asset
.
Vediamo ora come definire le proprietà height
e width
di Image
children: <Widget>[
// . . .
Container(
color: Colors.blueGrey,
child: Image.asset(
_PIC01,
height: 200,
width: 200,
),
)
],
Eseguendo l’applicazione otterremo il seguente risultato.
Figura 66. Caricamento di un’immagine tramite asset con dimensioni prefissate per a) Android e b) iOS (click per ingrandire)
Come possiamo notare, il ridimensionamento ha permesso a Flutter di scalare correttamente l’immagine senza generare distorsioni, ma allo stesso tempo ha lasciato degli spazi all’interno del container evidenziati dall sfondo blue-gray.
Per evitare questo effetto e far sì che l’immagine riempia appieno il container, possiamo ricorrere alla proprietà fit
per definire la modalità con cui visualizzare l’immagine come la proprietà BoxFit.cover
mostrata in precedenza. Oltre a questa, è possibile usare:
Proprietà | Descrizione |
---|---|
fill |
riempie il contenitore distorcendo le proporzioni dell’immagine |
fitWidth |
si assicura che l’intera larghezza dell’immagine sia mostrata e occupi il contenitore. Di conseguenza, l’immagine risulterà tagliata in altezza |
fitHeight |
si assicura che l’intera altezza dell’immagine sia mostrata e occupi il contenitore. L’immagine verrà tagliata in larghezza se è più larga del widget padre, altrimenti verranno mostrati degli spazi laterali |
Comprese queste tre proprietà vediamole in azione con un esempio chiarificatore e aggiungiamo i seguenti tre widget Image.
children: <Widget>[
// . . .
Container(
color: Colors.blueGrey,
child: Image.asset(
_PIC01,
fit: BoxFit.fill,
height: 150,
width: 150,
),
),
Container(
color: Colors.blueGrey,
child: Image.asset(
_PIC01,
fit: BoxFit.fitWidth,
height: 150,
width: 150,
),
),
Container(
color: Colors.blueGrey,
child: Image.asset(
_PIC01,
fit: BoxFit.fitHeight,
height: 150,
width: 150,
),
)
]
Eseguendo l’applicazione otterremo il seguente risultato.
Figura 67. Caricamento di un’immagine tramite asset definendo la proprietà fit per a) Android e b) iOS (click per ingrandire)
Oltre a queste proprietà, ve ne sono molte altre che possono essere utilizzate per decidere come mostrare le nostre immagini. Si rimanda alla documentazione di BoxFit
per vedere ulteriori dettagli, mentre per alcuni esempi pratici si rimanda al codice di questa lezione.
Proviamo adesso ad utilizzare la proprietà repeat
. Anche qui, ci sono diversi approcci tra cui repeatX
, che ripeterà l’immagine orizzontalmente. Vediamo un esempio.
Container(
constraints: BoxConstraints.expand(height: 50),
margin: EdgeInsets.only(bottom: 15),
child: Image.asset(
_PIC03,
repeat: ImageRepeat.repeatX,
fit: BoxFit.contain,
),
)
Eseguiamo ora l’app otterremo il seguente risultato.
Figura 68. Caricamento di un’immagine tramite asset ripetuta sul suo asse orizzontale per a) Android e b) iOS (click per ingrandire)
Altre due proprietà utili per l’applicazione di filtri semplici e veloci sono la combinazione di color
e colorBlend
. Ad esempio, immaginiamo di voler combinare il colore viola con l’immagine usando la modalità Color Dodge, il nostro codice sarà:
Container(
child: Image.asset(
_PIC04,
color: Colors.purple,
colorBlendMode: BlendMode.colorDodge,
),
)
Tramite l’esecuzione dell’app otterremo il seguente risultato.
Figura 69. Caricamento di un’immagine tramite asset e definizione delle proprietà color e colorBlendMode per a) Android e b) iOS (click per ingrandire)
Ovviamente, questa è solo una delle molteplici modalità definite da BlendMode
e si invita a consultare la documentazione ufficiale per saperne di più.
Vediamo infine la proprietà filterQuality
che definisce il livello di qualità dell’immagine da mostrare. Confrontiamo ad esempio la stessa immagine caricata con qualità high
e none
come segue.
children: <Widget>[
// . . .
Container(
color: Colors.blueGrey,
child: Image.asset(
_PIC05,
filterQuality: FilterQuality.none,
),
),
Container(
color: Colors.blueGrey,
child: Image.asset(
_PIC05,
filterQuality: FilterQuality.high,
),
),
]
A seguito dell’esecuzione dell’app, si può notare come l’immagine col filtro impostato a none
abbia delle sgranature rispetto all’immagine sottostante con livello del filtro impostato ad high
.
Figura 70. Caricamento di un’immagine tramite asset e definizione della proprietà filterQuality per a) Android e b) iOS (click per ingrandire)
Il codice di questa lezione è disponibile al seguente link.