Spesso è necessario presentare all’utente un insieme di elementi che hanno una rappresentazione omogenea, o quasi, per permettergli di selezionare uno di questi e continuare con l’utilizzo dell’app. Xamarin.Forms offre diverse soluzioni:
Elemento | Descrizione |
---|---|
ListView |
È una lista che tende ad avere un ampio numero di elementi dello stesso tipo, la cui presentazione è uniforme e consiste in elementi di tipo Cell di cui si può modificare lo stile |
Picker |
È una (piccola) lista di stringhe di cui è possibile selezionare un solo elemento, ed è impiegata come menu a tendina per la selezione |
TableView |
È un insieme di elementi di tipo Cell usate per mostrare dati o gestire gli input dell’utente. |
Una ListView è spesso impiegata in un’app, ed è essenziale modificarla in base alle esigenze. Per fare ciò in Xamarin, è fondamentale ricorrere a implementazioni personalizzate (o custom) delle ViewCell e ListView. In questa lezione vedremo come usare una ListView di base e come definire delle Cell
ad-hoc da mostrare nella lista.
Caricamento e binding dei dati
Il metodo più semplice per il caricamento dei dati è di assegnare ad una nuova ListView un elenco di risorse di tipo string
, usando la proprietà ItemSource
che accetta collezioni che implementano l’interfaccia IEnumerable
. Automaticamente viene invocato il metodo ToString
sulla collezione, mostrando i dati in una TextCell
. Vediamolo con un esempio.
Creiamo una nuova Form Xaml Page e impostiamo come nome ListViewExample. Supponiamo che la nostra app debba visualizzare un lista di ricette di cui abbiamo solo il nome. Per farlo, nel codice XAML basterà aggiungere quanto segue:
<ListView x:Name="recipes"/>
e nel code-behind impostiamo la collezione come segue:
recipes.ItemsSource = new string[]{
"Pasta Sfoglia",
"Pizza",
"Carbonara",
"Matriciana",
"Ragù"
};
ottenendo il seguente risultato:
In questo modo, non è possibile aggiungere un altro elemento alla lista poiché già popolata. Per ovviare al problema, è necessario impiegare una ObservableCollection<T>
. All’interno della classe inseriamo:
ObservableCollection<string> rcps = new ObservableCollection<string>();
Mentre nel costruttore sostituiamo il codice precedente con:
recipes.ItemsSource = rcps;
rcps.Add("Pasta Sfoglia");
rcps.Add("Pizza");
rcps.Add("Carbonara");
rcps.Add("Matriciana");
rcps.Add("Ragù");
rcps.Add("Salmone");
La visualizzazione dei contenuti rimarrà inalterata, ma sarà possibile aggiungere altri elementi.
Quanto fatto finora permette di utilizzare le TextCell
in automatico, avendo un’implementazione semplice e performante. Analogamente, potremmo usare le ImageCell
come elemento della ListView
. Ad esempio, definiamo una classe RecipeDataSource
(il cui codice è disponibile su GitHub) che fornirà l’insieme di ricette, composte da tre proprietà, e un metodo che restituisce una lista di ricette.
<ListView x:Name="recipes" ItemsSource="{Binding RecipeDataSource}">
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell ImageSource="{Binding ImagePath}" Text="{Binding Name}" Detail="{Binding Type}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Eseguendo il codice, otteniamo il seguente risultato:
Come si può notare, in WindowsPhone non si ha un ridimensionamento automatico dell’immagine, come invece accade su Android e iOS. Si tratta di un problema noto, come riportato nella documentazione ufficiale.
Una soluzione che offre la possibilità di creare contenuti ad-hoc per le ListView è creare una definizione personalizzata di Cell
per la rappresentazione degli elementi della lista. Ciò è possibile attraverso l’estensione della classe ViewCell
, che offre la possibilità di implementare uno specifico Renderer per ogni OS e di istanziare uno specifico controllo nativo, ad esempio le View
per Android.
Il primo passo da compiere è la creazione della nuova Cell
. Aggiungiamo al progetto portable una nuova classe con il nome di RecipeCustomCell
, che estenderà la classe ViewCell
:
public class RecipeCustomCell : ViewCell
Definiamo ora le sue proprietà. Ad esempio, per la proprietà Name
:
public static readonly BindableProperty NameProperty =
BindableProperty.Create("Name", typeof(string), typeof(RecipeCustomCell), "");
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
In questa definizione, sono state utilizzate le classi BindableProperty e BindableObject con i relativi metodi per fornire i meccanismi di propagazione dei cambiamenti tipici del binding.
Come prima, anche la definizione completa di questa classe è reperibile su GitHub.
Successivamente, dobbiamo impostare il DataTemplate
della proprietà ItemTemplate
della ListView con la nuova Cell
. Per farlo è necessario aggiungere al tag della ContentPage
il namespace e l’assembly in cui è definita la Cell
, attraverso la definizione xmlns:local
:
xmlns:local="clr-namespace:HelloXamarin;assembly=HelloXamarin"
e modificare la definizione del DataTemplate
:
<DataTemplate>
<local:RecipeCustomCell Name="{Binding Name}" Type="{Binding Type}" ImagePath="{Binding ImagePath}" />
</DataTemplate>
Abbiamo così definito per ogni riga della lista una RecipeCustomCell
composta da tre proprietà.
L’ultimo aspetto da analizzare riguarda la creazione di un nuovo Renderer per ogni piattaforma, passaggio necessario quando si lavora con le ViewCell
. È necessario creare una nuova classe che estenda la classe ViewCellRenderer
, che espone metodi come GetCellCore
per Android per creare il novo tipo di Cell
. Consideriamo, a titolo d’esempio, l’implementazione in Android.
Aggiungiamo al progetto Android la nuova classe RecipeAndroidCustomCellRender
, inserendo prima del namespace il codice seguente:
[assembly: ExportRenderer(typeof(RecipeCustomCell), typeof(RecipeAndroidCustomCellRender))]
Viene definito l’attributo ExportRenderer
che è usato per registrare il Renderer
con Xamarin.Forms. L’attributo si compone di due parametri: nome della Cell
e del Renderer
.
Resta quindi da sovrascrivere il metodo GetCellCore
che, prendendo in ingresso un oggetto di tipo Cell
, una View
, una ViewGroup
e il contesto corrente dell’app, restituisce la cella da mostrare come istanza di View in Android, sfruttando uno specifico layout ad-hoc. In particolare, il metodo può essere riassunto con il seguente schema a blocchi:
La relativa implementazione è riportata su GitHub.
Infine, per aggiungere il layout per la RecipeCustomCell
, è necessario aggiungere alla cartella Resources la cartella Layout e definire un nuovo file XML che conterrà il layout della Cell
, qui riportato.
Un processo analogo è compiuto per le altre due piattaforme:
iOS | sovrascrivere il metodo GetCell , come riportato qui, e aggiungere l’apposito layout, reperibile qui |
WindowsPhone | sovrascrivere il metodo GetTemplate , come riportato qui, e modificare il DataTemplate della classe App.xaml (codice) |
Effettuate queste modifiche, è possibile eseguire l’app su tutte e tre le piattaforme ottenendo il seguente risultato: