Compartir vía


Relleno de un control ListView de Xamarin.Android con datos

Para agregar filas a un elemento ListView, debe agregarlo al diseño e implementar una instancia de IListAdapter con métodos que ListView llama para rellenarse a sí mismo. Android incluye clases ListActivity y ArrayAdapter integradas que puede usar sin definir ningún código o XML de diseño personalizado. La clase ListActivity crea automáticamente una instancia de ListView expone una propiedad ListAdapter para proporcionar las vistas de fila que se van a mostrar mediante un adaptador.

Los adaptadores integrados toman un identificador de recurso de vista como parámetro que se usa para cada fila. Puede usar recursos integrados, como los de Android.Resource.Layout, por lo que no es necesario escribir otros propios.

Uso de ListActivity y ArrayAdapter<String>

En el ejemplo BasicTable/HomeScreen.cs se muestra cómo usar estas clases para mostrar una instancia de ListView en solo unas pocas líneas de código:

[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);
   }
}

Control de clics de fila

Normalmente, ListView también permitirá al usuario tocar una fila para realizar alguna acción (como reproducir una canción, llamar a un contacto o mostrar otra pantalla). Para responder a los toques del usuario, debe haber un método más implementado en ListActivity: OnListItemClick, como este:

Captura de pantalla de 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();
}

Ahora el usuario puede tocar una fila y aparecerá una alerta Toast:

Captura de pantalla de notificación del sistema que aparece cuando se toca una fila

Implementación de una instancia de ListAdapter

ArrayAdapter<string> es genial debido a su simplicidad, pero es extremadamente limitado. Pero a menudo tiene una colección de entidades empresariales, en lugar de solo cadenas que quiere enlazar. Por ejemplo, si los datos constan de una colección de clases Employee, es posible que quiera que la lista solo muestre los nombres de cada empleado. A fin de personalizar el comportamiento de ListView para controlar qué datos se muestran, debe implementar una subclase de BaseAdapter que invalide los cuatro elementos siguientes:

  • Count: para indicar al control cuántas filas hay en los datos.

  • GetView: para devolver una vista para cada fila, rellenada con datos. Este método tiene un parámetro para que ListView pase una fila existente sin usar para volverla a utilizar.

  • GetItemId: devuelve un identificador de fila (normalmente el número de fila, aunque puede ser cualquier valor largo que prefiera).

  • Indizador this[int]: para devolver los datos asociados a un número de fila determinado.

En el código de ejemplo de BasicTableAdapter/HomeScreenAdapter.cs se muestra cómo crear una subclase de 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 de un adaptador personalizado

El uso del adaptador personalizado es similar a la instancia integrada de ArrayAdapter, y se pasan context y string[] de los valores que se van a mostrar:

ListAdapter = new HomeScreenAdapter(this, items);

Como en este ejemplo se usa el mismo diseño de fila (SimpleListItem1), la aplicación resultante tendrá un aspecto idéntico al ejemplo anterior.

Reutilización de la vista de fila

En este ejemplo solo hay seis elementos. Como en la pantalla caben ocho, no es necesario volver a usar filas. Pero al mostrar cientos o miles de filas, sería un desperdicio de memoria crear cientos o miles de objetos View cuando solo caben ocho en la pantalla a la vez. Para evitar esta situación, cuando una fila desaparece de la pantalla, su vista se coloca en una cola para volver a usarse. Cuando el usuario se desplaza, ListView llama a GetView para solicitar nuevas vistas que mostrar: si están disponibles, pasa una vista sin usar en el parámetro convertView. Si este valor es null, el código debe crear una instancia de vista; de lo contrario, puede volver a establecer las propiedades de ese objeto y reutilizarlo.

El método GetView debe seguir este patrón para reutilizar vistas de fila:

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;
}

Las implementaciones del adaptador personalizado siempre deben reutilizar el objeto convertView antes de crear vistas para asegurarse de que no se agote la memoria al mostrar listas largas.

Algunas implementaciones del adaptador (como CursorAdapter) no tienen un método GetView, sino que requieren dos métodos diferentes, NewView y BindView, que aplican la reutilización de filas mediante la separación de las responsabilidades de GetView en dos métodos. Más adelante en el documento verá un ejemplo de CursorAdapter.

Habilitación del desplazamiento rápido

El desplazamiento rápido ayuda al usuario a desplazarse por listas largas proporcionando un "identificador" adicional que actúa como una barra de desplazamiento para acceder directamente a una parte de la lista. En esta captura de pantalla se muestra el controlador de desplazamiento rápido:

Captura de pantalla del desplazamiento rápido con un identificador de desplazamiento

Para que el controlador de desplazamiento rápido aparezca solo hay que establecer la propiedad FastScrollEnabled en true:

ListView.FastScrollEnabled = true;

Adición de un índice de sección

Un índice de sección proporciona comentarios adicionales para los usuarios cuando se desplazan rápidamente por una larga lista: muestra a qué "sección" se han desplazado. Para que el índice de sección aparezca, la subclase Adapter debe implementar la interfaz ISectionIndexer para proporcionar el texto del índice en función de las filas que se muestran:

Captura de pantalla de H que aparece en la sección anterior que comienza con H

Para implementar ISectionIndexer, debe agregar tres métodos a un adaptador:

  • GetSections: proporciona la lista completa de títulos de índice de sección que se pueden mostrar. Este método necesita una matriz de objetos de Java, por lo que el código debe crear una instancia de Java.Lang.Object[] a partir de una colección de .NET. En el ejemplo, devuelve una lista de los caracteres iniciales de la lista como Java.Lang.String.

  • GetPositionForSection: devuelve la primera posición de fila para un índice de sección determinado.

  • GetSectionForPosition: devuelve el índice de sección que se va a mostrar para una fila determinada.

El archivo de ejemplo SectionIndex/HomeScreenAdapter.cs implementa esos métodos y algún código adicional en el constructor. Para crear el índice de sección, el constructor recorre en bucle todas las filas y extrae el primer carácter del título (para que esto funcione los elementos ya deben estar ordenados).

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 las estructuras de datos creadas, los métodos ISectionIndexer son muy sencillos:

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;
}

Los títulos de índice de sección no necesitan asignar 1:1 a las secciones reales. Por este motivo existe el método GetPositionForSection. GetPositionForSection le ofrece la oportunidad de asignar los índices que se encuentran en la lista de índices a las secciones que se encuentren en la vista de lista. Por ejemplo, es posible que tenga una "z" en el índice, pero que no tenga una sección de tabla para cada letra, por lo que, en lugar de asignar "z" a 26, se puede asignar a 25 o 24, o a cualquier índice de sección al que se debe asignar "z".