Lo spinner è un altro controllo molto comune, è il classico menu a tendina. In Android viene realizzato come AdapterView e tanto basta per farci comprendere il modo in cui dovremo usarlo.
Si è visto nelle lezioni precedenti che tutti gli AdapterView vengono grosso modo usati alla stessa maniera. È sufficiente collegare loro un Adapter che incapsula la logica di produzione delle View.
Per il resto l'AdapterView si occuperà di gestire gli eventi.
Spinner con valori fissi
Comunque lo Spinner trova la sua utilità anche in contesti meno complessi in cui si può usare come normale campo form per selezionare un valore in un dato insieme.
Pensiamo ad un form in cui si inseriscono i dati di una persona. Al momento di definire lo stato civile, la scelta ricade su un set di possibilità prestabilite: coniugato/a, divorziato/a, celibe/nubile, separato/a.
Il controllo ideale per effettuare questa scelta è senza dubbio lo Spinner . In questo caso, si potrebbe sentire meno il bisogno dell'Adapter in quanto la sorgente dati non cambierà più visto che vengono annoverati già tutti gli stati civili possibili.
In questo caso, si può procedere agendo solo tra risorse XML:
- si crea un array di risorse stringa in un file della cartella res/values e lo si completa con tutti i valori necessari:
La risorsa sarà accessibile in XML mediante<string-array name="staticivili"> <item>Divorziato/a</item> <item>Separato/a</item> <item>Coniugato/a</item> <item>Celibe/Nubile</item> </string-array>
@array/staticivili
- nel file di layout in cui si trova lo Spinner si effettua una modifica. Si aggiunge l'attributo
android:entries
e gli si assegna la risorsa di stringhe a cui accedere:
<Spinner android:id="@+id/spinner" android:layout_width="wrap_content" android:layout_height="wrap_content" android:entries="@array/staticivili"/>
Seguendo questi due semplici passi, nel layout troveremo lo spinner già popolato dei valori. Non è stato necessario apportare alcuna modifica nel codice Java e tantomeno istanziare direttamente un adapter.
Spinner con Adapter
Il comportamento dello Spinner legato ad un adapter è in buona parte uguale a quello della ListView e della GridView.
Vedremo ora un esempio che mostra un uso congiunto di Spinner e ListView in cui:
- lo Spinner mostra un elenco di Paesi;
- la ListView visualizza un elenco di città appartenenti tutte al Paese selezionato nello Spinner.
L'esempio ha anche il pregio di riepilogare molti concetti visti sinora nello studio delle GUI quindi lo si consideri un esercizio di validità generale.
L'immagine seguente mostra le varie fasi di funzionamento come appaiono in un emulatore.
La fonte dei dati sarà una classe Java, molto semplice, che con liste e mappe fornirà i dati necessari all'esempio:
public class CountryList
{
private HashMap<String,ArrayList<String>> list;
public CountryList()
{
list=new HashMap<String, ArrayList<String>>();
ArrayList<String> cities=new ArrayList<String>();
cities.add("Roma");
cities.add("Torino");
cities.add("Firenze");
list.put("Italia", cities);
cities=new ArrayList<String>();
cities.add("Parigi");
cities.add("Lione");
cities.add("Marsiglia");
list.put("Francia", cities);
cities=new ArrayList<String>();
cities.add("Madrid");
cities.add("Barcellona");
list.put("Spagna", cities);
}
public Collection<String> getCountries()
{
return list.keySet();
}
public Collection<String> getCitiesByCountry(String c)
{
return list.get(c);
}
}
Il layout dell'Activity è molto semplice (file: res/layout/activity_main.xml), un RelativeLayout che mostra entrambe le View:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Spinner
android:layout_width="@dimen/body_width"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/margin_top_1"
android:id="@+id/countries"
/>
<ListView
android:layout_width="@dimen/body_width"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@+id/countries"
android:layout_marginTop="@dimen/margin_top_2"
android:id="@+id/cities"/>
</RelativeLayout>
mentre la forma che avrà la singola riga dello Spinner e della ListView sarà la seguente (file: res/layout/row.xml):
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:textSize="25sp"
android:id="@+id/rowtext" />
Tenere presente che quando nel codice dell'Activity richiameremo l'id R.id.rowtext ci riferiremo alla TextView compresa in questo layout.
Il codice dell'Activity non offre grandi sorprese:
public class MainActivity extends AppCompatActivity
{
private CountryList countries=new CountryList();
private ArrayAdapter<String> listviewAdapter;
private ArrayAdapter<String> spinnerAdapter;
@Override
protected void onCreate(Bundle savedInstanceState)
{
// assegnazione del layout all'Activity
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// preparazione della ListView per l'elenco delle città
ListView lv=findViewById(R.id.cities);
listviewAdapter=new ArrayAdapter<String>(this, R.layout.row);
lv.setAdapter(listviewAdapter);
// preparazione dello Spinner per mostrare l'elenco dei Paesi
spinnerAdapter=new ArrayAdapter<String>(this, R.layout.row);
spinnerAdapter.addAll(countries.getCountries());
Spinner sp= findViewById(R.id.countries);
sp.setAdapter(spinnerAdapter);
sp.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
{
@Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
TextView txt= arg1.findViewById(R.id.rowtext);
String s=txt.getText().toString();
updateCities(s);
}
@Override
public void onNothingSelected(AdapterView<?> arg0)
{ }
});
}
private void updateCities(String city)
{
ArrayList<String> l=(ArrayList<String>)
countries.getCitiesByCountry(city);
listviewAdapter.clear();
listviewAdapter.addAll(l);
}
}
Da notare comunque che:
- essendo entrambi AdapterView, si sono svolte le stesse operazioni per Spinner e ListView. In entrambi si è preparato un Adapter che gli è stato collegato con il metodo
setAdapter
; - la gestione degli eventi è stata usata solo per lo Spinner. Avviene nella maniera classica illustrata nel capitolo delle View;
- all'interno del metodo
onItemSelected
che gestisce la selezione di una voce dello Spinner viene invocato il metodoupdateCities
che aggiorna mediante l'adapter della ListView l'elenco delle città. Viene fatto in maniera molto semplice ricaricando una nuova lista.