Popolamento di un controllo ListView Xamarin.Android con dati

Per aggiungere righe a un ListView oggetto è necessario aggiungerlo al layout e implementare un oggetto IListAdapter con metodi che le ListView chiamate popolano autonomamente. Android include classi e ArrayAdapter predefinite ListActivity che è possibile usare senza definire codice o XML di layout personalizzato. La ListActivity classe crea automaticamente un oggetto ListView ed espone una ListAdapter proprietà per fornire le visualizzazioni di riga da visualizzare tramite un adattatore.

Gli adattatori predefiniti accettano un ID risorsa di visualizzazione come parametro che viene usato per ogni riga. È possibile usare risorse predefinite, ad esempio quelle in Android.Resource.Layout , in modo da non dover scrivere le proprie.

Uso di ListActivity e della stringa ArrayAdapter<>

Nell'esempio BasicTable/HomeScreen.cs viene illustrato come usare queste classi per visualizzare un oggetto ListView in poche righe di codice:

[Activity(Label = "BasicTable", MainLauncher = true, Icon = "@drawable/icon")]
public class HomeScreen : ListActivity {
   string[] items;
   protected override void OnCreate(Bundle bundle)
   {
       base.OnCreate(bundle);
       items = new string[] { "Vegetables","Fruits","Flower Buds","Legumes","Bulbs","Tubers" };
       ListAdapter = new ArrayAdapter<String>(this, Android.Resource.Layout.SimpleListItem1, items);
   }
}

Gestione dei clic delle righe

In genere un ListView consentirà anche all'utente di toccare una riga per eseguire un'azione (ad esempio la riproduzione di una canzone, o la chiamata a un contatto o la visualizzazione di un'altra schermata). Per rispondere ai tocco dell'utente, è necessario implementare un altro metodo in ListActivity , OnListItemClick come illustrato di seguito:

Screenshot of a SimpleListItem

protected override void OnListItemClick(ListView l, View v, int position, long id)
{
   var t = items[position];
   Android.Widget.Toast.MakeText(this, t, Android.Widget.ToastLength.Short).Show();
}

Ora l'utente può toccare una riga e verrà visualizzato un Toast avviso:

Screenshot of Toast that appears when a row is touched

Implementazione di un listAdapter

ArrayAdapter<string> è grande a causa della sua semplicità, ma è estremamente limitato. Tuttavia, spesso si dispone di una raccolta di entità aziendali, anziché solo di stringhe da associare. Ad esempio, se i dati sono costituiti da una raccolta di classi Employee, è possibile che l'elenco visualizzi solo i nomi di ogni dipendente. Per personalizzare il comportamento di un ListView oggetto per controllare i dati visualizzati, è necessario implementare una sottoclasse dell'override dei BaseAdapter quattro elementi seguenti:

  • Count : per indicare al controllo il numero di righe presenti nei dati.

  • GetView : per restituire una visualizzazione per ogni riga, popolata con i dati. Questo metodo ha un parametro per il ListView passaggio di una riga esistente inutilizzata per il riutilizzo.

  • GetItemId : restituisce un identificatore di riga (in genere il numero di riga, anche se può essere qualsiasi valore lungo desiderato).

  • indicizzatore this[int] : per restituire i dati associati a un determinato numero di riga.

Il codice di esempio in BasicTableAdapter/HomeScreenAdapter.cs illustra come sottoclasse BaseAdapter:

public class HomeScreenAdapter : BaseAdapter<string> {
   string[] items;
   Activity context;
   public HomeScreenAdapter(Activity context, string[] items) : base() {
       this.context = context;
       this.items = items;
   }
   public override long GetItemId(int position)
  {
       return position;
   }
   public override string this[int position] {  
       get { return items[position]; }
   }
   public override int Count {
       get { return items.Length; }
   }
   public override View GetView(int position, View convertView, ViewGroup parent)
   {
       View view = convertView; // re-use an existing view, if one is available
      if (view == null) // otherwise create a new one
           view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItem1, null);
       view.FindViewById<TextView>(Android.Resource.Id.Text1).Text = items[position];
       return view;
   }
}

Uso di un adattatore personalizzato

L'uso dell'adattatore personalizzato è simile al valore predefinito ArrayAdapter, passando un context e i string[] valori da visualizzare:

ListAdapter = new HomeScreenAdapter(this, items);

Poiché in questo esempio viene usato lo stesso layout di riga (SimpleListItem1) l'applicazione risultante sarà identica all'esempio precedente.

Riesezione della visualizzazione riga

In questo esempio sono presenti solo sei elementi. Poiché lo schermo può adattarsi a otto, non è necessario riutilizzare la riga. Quando si visualizzano centinaia o migliaia di righe, tuttavia, si tratta di uno spreco di memoria per creare centinaia o migliaia di View oggetti quando solo otto si adattano allo schermo alla volta. Per evitare questa situazione, quando una riga scompare dallo schermo, la visualizzazione viene inserita in una coda per il riutilizzo. Quando l'utente scorre, le ListView chiamate GetView per richiedere la visualizzazione di nuove visualizzazioni, se disponibili passano una visualizzazione inutilizzata nel convertView parametro . Se questo valore è Null, il codice deve creare una nuova istanza di visualizzazione. In caso contrario, è possibile riimpostati le proprietà dell'oggetto e usarlo di nuovo.

Il GetView metodo deve seguire questo modello per riutilizzare le visualizzazioni di riga:

public override View GetView(int position, View convertView, ViewGroup parent)
{
   View view = convertView; // re-use an existing view, if one is supplied
   if (view == null) // otherwise create a new one
       view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItem1, null);
   // set view properties to reflect data for the given row
   view.FindViewById<TextView>(Android.Resource.Id.Text1).Text = items[position];
   // return the view, populated with data, for display
   return view;
}

Le implementazioni dell'adattatore personalizzato devono sempre riutilizzare l'oggetto convertView prima di creare nuove visualizzazioni per assicurarsi che non esauriscano la memoria durante la visualizzazione di elenchi lunghi.

Alcune implementazioni dell'adapter CursorAdapter(ad esempio ) non hanno un GetView metodo, ma richiedono due metodi NewView diversi e BindView che impongono il riutilizzo delle righe separando le responsabilità di GetView in due metodi. Più avanti nel documento è disponibile un CursorAdapter esempio.

Abilitazione dello scorrimento rapido

Lo scorrimento rapido consente all'utente di scorrere gli elenchi lunghi fornendo un 'handle' aggiuntivo che funge da barra di scorrimento per accedere direttamente a una parte dell'elenco. Questo screenshot mostra l'handle di scorrimento rapido:

Screenshot of fast-scrolling with a scroll handle

La visualizzazione dell'handle di scorrimento rapido è semplice come l'impostazione della FastScrollEnabled proprietà su true:

ListView.FastScrollEnabled = true;

Aggiunta di un indice di sezione

Un indice di sezione fornisce un feedback aggiuntivo per gli utenti quando scorre rapidamente un lungo elenco, che mostra la "sezione" a cui è stato eseguito lo scorrimento. Per fare in modo che l'indice di sezione venga visualizzato nella sottoclasse Adapter deve implementare l'interfaccia ISectionIndexer per fornire il testo dell'indice a seconda delle righe visualizzate:

Screenshot of H appearing above section that starts with H

Per implementare ISectionIndexer è necessario aggiungere tre metodi a un adattatore:

  • GetSections : fornisce l'elenco completo dei titoli degli indici di sezione che possono essere visualizzati. Questo metodo richiede una matrice di oggetti Java in modo che il codice debba creare un oggetto Java.Lang.Object[] da una raccolta .NET. Nell'esempio viene restituito un elenco dei caratteri iniziali nell'elenco come Java.Lang.String .

  • GetPositionForSection : restituisce la prima posizione della riga per un indice di sezione specificato.

  • GetSectionForPosition : restituisce l'indice di sezione da visualizzare per una determinata riga.

Il file di esempio SectionIndex/HomeScreenAdapter.cs implementa tali metodi e un codice aggiuntivo nel costruttore. Il costruttore compila l'indice di sezione eseguendo un ciclo in ogni riga ed estraendo il primo carattere del titolo (gli elementi devono essere già ordinati per il funzionamento di questa operazione).

alphaIndex = new Dictionary<string, int>();
for (int i = 0; i < items.Length; i++) { // loop through items
   var key = items[i][0].ToString();
   if (!alphaIndex.ContainsKey(key))
       alphaIndex.Add(key, i); // add each 'new' letter to the index
}
sections = new string[alphaIndex.Keys.Count];
alphaIndex.Keys.CopyTo(sections, 0); // convert letters list to string[]

// Interface requires a Java.Lang.Object[], so we create one here
sectionsObjects = new Java.Lang.Object[sections.Length];
for (int i = 0; i < sections.Length; i++) {
   sectionsObjects[i] = new Java.Lang.String(sections[i]);
}

Con le strutture di dati create, i ISectionIndexer metodi sono molto semplici:

public Java.Lang.Object[] GetSections()
{
   return sectionsObjects;
}
public int GetPositionForSection(int section)
{
   return alphaIndexer[sections[section]];
}
public int GetSectionForPosition(int position)
{   // this method isn't called in this example, but code is provided for completeness
    int prevSection = 0;
    for (int i = 0; i < sections.Length; i++)
    {
        if (GetPositionForSection(i) > position)
        {
            break;
        }
        prevSection = i;
    }
    return prevSection;
}

I titoli degli indici di sezione non devono eseguire il mapping di 1:1 alle sezioni effettive. Ecco perché il GetPositionForSection metodo esiste. GetPositionForSection offre l'opportunità di eseguire il mapping di qualsiasi indice nell'elenco di indici a qualsiasi sezione della visualizzazione elenco. Ad esempio, è possibile che nell'indice sia presente una sezione "z", ma potrebbe non essere presente una sezione della tabella per ogni lettera, quindi anziché il mapping "z" a 26, potrebbe essere mappato a 25 o 24 o qualsiasi indice di sezione "z" a cui eseguire il mapping.