Compartir vía


Personalización de la apariencia de un elemento ListView con Xamarin.Android

La apariencia de los elementos ListView la dicta el diseño de las filas que se muestran. Para cambiar la apariencia de ListView, use un diseño de filas diferente.

Vistas de fila integradas

Hay doce vistas integradas a las que se puede hacer referencia mediante Android.Resource.Layout:

  • TestListItem: una sola línea de texto con un formato mínimo.

  • SimpleListItem1: una sola línea de texto.

  • SimpleListItem2: dos líneas de texto.

  • SimpleSelectableListItem : una sola línea de texto que admite la selección de uno o varios elementos (se agrega en el nivel 11 de API).

  • SimpleListItemActivated1 : similar a SimpleListItem1, pero el color de fondo indica cuándo se selecciona una fila (se agrega en el nivel 11 de API).

  • SimpleListItemActivated2 : similar a SimpleListItem2, pero el color de fondo indica cuándo se selecciona una fila (se agrega en el nivel 11 de API).

  • SimpleListItemChecked : muestra marcas de verificación para indicar una selección.

  • SimpleListItemMultipleChoice: muestra casillas para indicar una selección múltiple.

  • SimpleListItemSingleChoice: muestra botones de radio para indicar una selección mutuamente exclusiva.

  • TwoLineListItem: dos líneas de texto.

  • ActivityListItem: una sola línea de texto con una imagen.

  • SimpleExpandableListItem: agrupa filas por categorías y todos los grupos se pueden expandir o contraer.

Todas las vistas de fila integradas tiene un estilo integrado asociado. Estas capturas de pantalla muestran cómo aparece cada vista:

Capturas de pantalla de TestListItem, SimpleSelectableListItem, SimpleListitem1 y SimpleListItem2

Capturas de pantalla de SimpleListItemActivated1, SimpleListItemActivated2, SimpleListItemChecked y SimpleListItemMultipleChecked

Capturas de pantalla de SimpleListItemSingleChoice, TwoLineListItem, ActivityListItem y SimpleExpandableListItem

El archivo de ejemplo BuiltInViews/HomeScreenAdapter.cs (en la solución BuiltInViews) contiene el código necesario para generar las pantallas de elementos de lista no expandibles. La vista se establece en el método GetView de la siguiente manera:

view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItem1, null);

Luego, para establecer las propiedades de la vista es preciso hacer referencia a los identificadores de controles estándar Text1, Text2 y Icon en Android.Resource.Id (no establezca propiedades que la vista no contenga, ya que si lo hace, se producirá una excepción):

view.FindViewById<TextView>(Android.Resource.Id.Text1).Text = item.Heading;
view.FindViewById<TextView>(Android.Resource.Id.Text2).Text = item.SubHeading;
view.FindViewById<ImageView>(Android.Resource.Id.Icon).SetImageResource(item.ImageResourceId); // only use with ActivityListItem

El archivo de ejemplo BuiltInExpandableViews/ExpandableScreenAdapter.cs (en la solución BuiltInViews) contiene el código necesario para generar la pantalla SimpleExpandableListItem. La vista de grupo se establece en el método GetGroupView de la siguiente manera:

view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleExpandableListItem1, null);

La vista secundaria se establece en el método GetChildView de la siguiente manera:

view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleExpandableListItem2, null);

Las propiedades de la vista de grupo y de la vista secundaria se pueden establecer haciendo referencia a los identificadores de los controles Text1 y Text2 estándar, tal como se ha mostrado. La captura de pantalla de SimpleExpandableListItem (mostrada anteriormente) muestra un ejemplo de una vista de grupo de una línea (SimpleExpandableListItem1) y una vista secundaria de dos líneas (SimpleExpandableListItem2). Como alternativa, la vista de grupo se puede configurar para dos líneas (SimpleExpandableListItem2) y la vista secundaria se puede configurar para una línea (SimpleExpandableListItem1), o bien tanto la vista de grupo como la vista secundaria pueden tener el mismo número de líneas.

Accesorios

Las filas pueden tener accesorios agregados a la derecha de la vista para indicar el estado de selección:

  • SimpleListItemChecked: crea una sola lista de selección con una marca como indicador.

  • SimpleListItemSingleChoice: crea listas de tipo botón de radio en las que solo se puede elegir una opción.

  • SimpleListItemMultipleChoice : crea listas de tipo casilla en las que se pueden elegir varias opciones.

Los accesorios indicados se ilustran en las siguientes pantallas, en su orden respectivo:

Capturas de pantalla de SimpleListItemChecked, SimpleListItemSingleChoice y SimpleListItemMultipleChoice con accesorios

Para mostrar uno de estos accesorios, pase el id. del recurso de diseño necesario al adaptador y, después, establezca manualmente el estado de selección de las filas necesarias. Esta línea de código muestra cómo crear y asignar un Adapter mediante uno de estos diseños:

ListAdapter = new ArrayAdapter<String>(this, Android.Resource.Layout.SimpleListItemChecked, items);

El propio ListView admite diferentes modos de selección, independientemente del accesorio que se muestre. Para evitar confusiones, use el modo de selección Single con los accesorios de SingleChoice y los modos Checked o Multiple con el estilo MultipleChoice. El modo de selección lo controla la propiedad ChoiceMode de ListView.

Control del nivel de API

Las versiones anteriores de Xamarin.Android implementaban enumeraciones como propiedades de enteros. La más reciente ha introducido los tipos de enumeración de .NET adecuados, lo que facilita mucho la detección de las posibles opciones.

En función del nivel de API al que se dirija, ChoiceMode será un entero o una enumeración. El archivo de ejemplo AccessoryViews/HomeScreen.cs tiene un bloque con comentarios por si desea que el destino sea API Gingerbread:

// For targeting Gingerbread the ChoiceMode is an int, otherwise it is an
// enumeration.

lv.ChoiceMode = Android.Widget.ChoiceMode.Single; // 1
//lv.ChoiceMode = Android.Widget.ChoiceMode.Multiple; // 2
//lv.ChoiceMode = Android.Widget.ChoiceMode.None; // 0

// Use this block if targeting Gingerbread or lower
/*
lv.ChoiceMode = 1; // Single
//lv.ChoiceMode = 0; // none
//lv.ChoiceMode = 2; // Multiple
//lv.ChoiceMode = 3; // MultipleModal
*/

Selección de elementos mediante programación

Para establecer manualmente los elementos que se "seleccionan" se utiliza el método SetItemChecked (se puede llamar varias veces si se desea seleccionar varias opciones):

// Set the initially checked row ("Fruits")
lv.SetItemChecked(1, true);

El código también debe detectar las selecciones individuales de forma diferente que las selecciones múltiples. Para determinar qué fila se ha seleccionado en el modo Single, use la propiedad integer de CheckedItemPosition:

FindViewById<ListView>(Android.Resource.Id.List).CheckedItemPosition

Para determinar qué filas se han seleccionado en Multiple modo, debe recorrer en bucle .CheckedItemPositions SparseBooleanArray Una matriz dispersa es como un diccionario que solo contiene entradas en las que se ha cambiado el valor, por lo que debe recorrerla entera buscando valores true para saber qué se ha seleccionado en la lista, como se muestra en el siguiente fragmento de código:

var sparseArray = FindViewById<ListView>(Android.Resource.Id.List).CheckedItemPositions;
for (var i = 0; i < sparseArray.Size(); i++ )
{
   Console.Write(sparseArray.KeyAt(i) + "=" + sparseArray.ValueAt(i) + ",");
}
Console.WriteLine();

Creación de diseños de fila personalizados

Las cuatro vistas de fila integradas son muy simples. Para mostrar diseños más complejos (como una lista de correos electrónicos, tweets o información de contacto), se requiere una vista personalizada. Habitualmente, las vistas personalizadas se declaran como archivos AXML en el directorio Resources/Layout y, después, se cargan con su identificador de recurso mediante un adaptador personalizado. La vista puede contener cualquier número de clases de visualización (como los controles TextView o ImageView, entre otros) con colores, fuentes y diseño personalizados.

Este ejemplo se diferencia en varios factores de los anteriores:

  • Se hereda de Activity, no de ListActivity. Puede personalizar filas para cualquier ListView, pero también se pueden incluir otros controles en un diseño de Activity (como un título, botones u otros elementos de la interfaz de usuario). En este ejemplo se agrega un título encima de ListView para ilustrarlo.

  • Requiere un archivo de diseño AXML para la pantalla; en los ejemplos anteriores, ListActivity no requiere un archivo de diseño. Este AXML contiene una declaración del control ListView.

  • Requiere un archivo de diseño AXML para representar cada fila. Este archivo AXML contiene los controles de texto e imagen con la configuración de fuente y color personalizada.

  • Usa un archivo XML del selector personalizado opcional para establecer la apariencia de la fila cuando se selecciona.

  • La implementación de Adapter devuelve un diseño personalizado de la invalidación de GetView.

  • ItemClick debe declararse de forma diferente (se adjunta un controlador de eventos a ListView.ItemClick, en lugar de una invalidación OnListItemClick en ListActivity).

Estos cambios se detallan a continuación, empezando por crear la vista de la actividad y la vista de fila personalizada y, después, cubrir las modificaciones en el adaptador y la actividad para representarlos.

Incorporación de un control ListView a un diseño de actividad

Como HomeScreen no hereda de ListActivity no tiene una vista predeterminada, por lo que se debe crear un archivo AXML de diseño para la vista de HomeScreen. Para este ejemplo, la vista tendrá un título (que utiliza TextView) y u ListView para mostrar los datos. El diseño se define en el archivo Resources/Layout/HomeScreen.axml que se muestra aquí:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="vertical"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent">
    <TextView android:id="@+id/Heading"
        android:text="Vegetable Groups"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#00000000"
        android:textSize="30dp"
        android:textColor="#FF267F00"
        android:textStyle="bold"
        android:padding="5dp"
    />
    <ListView android:id="@+id/List"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:cacheColorHint="#FFDAFF7F"
    />
</LinearLayout>

La ventaja de usar un Activity con un diseño personalizado (en lugar de un ListActivity) radica en poder agregar controles adicionales a la pantalla, como el título TextView de este ejemplo.

Creación de un diseño de fila personalizado

Se requiere otro archivo de diseño AXML que contenga el diseño personalizado de cada fila que aparecerá en la vista de lista. En este ejemplo, la fila tendrá un fondo verde, texto marrón y una imagen alineada a la derecha. El marcado XML de Android para declarar este diseño se describe en Resources/Layout/CustomView.axml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout  xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   android:background="#FFDAFF7F"
   android:padding="8dp">
    <LinearLayout android:id="@+id/Text"
       android:orientation="vertical"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:paddingLeft="10dip">
        <TextView
         android:id="@+id/Text1"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textColor="#FF7F3300"
         android:textSize="20dip"
         android:textStyle="italic"
         />
        <TextView
         android:id="@+id/Text2"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textSize="14dip"
         android:textColor="#FF267F00"
         android:paddingLeft="100dip"
         />
    </LinearLayout>
    <ImageView
        android:id="@+id/Image"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:padding="5dp"
        android:src="@drawable/icon"
        android:layout_alignParentRight="true" />
</RelativeLayout >

Aunque los diseños de fila personalizados pueden contener muchos controles diferentes, el rendimiento del desplazamiento puede verse afectado por diseños complejos y por el uso de imágenes (especialmente si tienen que cargarse a través de la red). Para más información sobre cómo solucionar problemas de rendimiento de desplazamiento, consulte el artículo de Google.

Referencia a una vista de fila personalizada

La implementación del ejemplo de adaptador personalizado está en HomeScreenAdapter.cs. El método de clave es GetView, donde carga el AXML personalizado mediante el id. de recurso Resource.Layout.CustomView y, después, establece propiedades en cada uno de los controles de la vista antes de devolverlo. Se muestra la clase de adaptador completa:

public class HomeScreenAdapter : BaseAdapter<TableItem> {
   List<TableItem> items;
   Activity context;
   public HomeScreenAdapter(Activity context, List<TableItem> items)
       : base()
   {
       this.context = context;
       this.items = items;
   }
   public override long GetItemId(int position)
   {
       return position;
   }
   public override TableItem this[int position]
   {
       get { return items[position]; }
   }
   public override int Count
   {
       get { return items.Count; }
   }
   public override View GetView(int position, View convertView, ViewGroup parent)
   {
       var item = items[position];
       View view = convertView;
       if (view == null) // no view to re-use, create new
           view = context.LayoutInflater.Inflate(Resource.Layout.CustomView, null);
       view.FindViewById<TextView>(Resource.Id.Text1).Text = item.Heading;
       view.FindViewById<TextView>(Resource.Id.Text2).Text = item.SubHeading;
       view.FindViewById<ImageView>(Resource.Id.Image).SetImageResource(item.ImageResourceId);
       return view;
   }
}

Referencia a ListView personalizado en la actividad

Dado que la clase HomeScreen ahora hereda de Activity, se declara un campo ListView en la clase que contendrá una referencia al control declarado en AXML:

ListView listView;

Luego, la clase debe cargar el AXML del diseño personalizado de la actividad mediante el método SetContentView. Luego, puede buscar el control ListView en el diseño y, después, crea el adaptador y lo asigna, y también asigna el controlador de clics. El código del método OnCreate se muestra aquí:

SetContentView(Resource.Layout.HomeScreen); // loads the HomeScreen.axml as this activity's view
listView = FindViewById<ListView>(Resource.Id.List); // get reference to the ListView in the layout

// populate the listview with data
listView.Adapter = new HomeScreenAdapter(this, tableItems);
listView.ItemClick += OnListItemClick;  // to be defined

Por último, se debe definir el controlador de ItemClick; en este caso, solo muestra un mensaje Toast:

void OnListItemClick(object sender, AdapterView.ItemClickEventArgs e)
{
   var listView = sender as ListView;
   var t = tableItems[e.Position];
   Android.Widget.Toast.MakeText(this, t.Heading, Android.Widget.ToastLength.Short).Show();
}

La clase resultante es así:

Captura de pantalla de CustomRowView resultante

Personalización del color del selector de filas

Cuando se toca una fila, debe resaltarse para los comentarios del usuario. Cuando una vista personalizada especifica un color de fondo como lo hace CustomView.axml, también invalida el resaltado de la selección. Esta línea de código de CustomView.axml establece el fondo en verde claro, pero también significa que no hay ningún indicador visual cuando se toca la fila:

android:background="#FFDAFF7F"

Para volver a habilitar el comportamiento del resaltado y también para personalizar el color que se usa, establezca el atributo background en un selector personalizado. El selector declarará tanto el color de fondo predeterminado como el color del resaltado. El archivo Resources/Drawable/CustomSelector.xml contiene la siguiente declaración:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false"
  android:state_selected="false"
  android:drawable="@color/cellback" />
<item android:state_pressed="true" >
  <shape>
     <gradient
      android:startColor="#E77A26"
        android:endColor="#E77A26"
        android:angle="270" />
  </shape>
</item>
<item android:state_selected="true"
  android:state_pressed="false"
  android:drawable="@color/cellback" />
</selector>

Para hacer referencia al selector personalizado, cambie el atributo background de CustomView.axml a:

android:background="@drawable/CustomSelector"

Una fila seleccionada y el mensaje Toast correspondiente ahora tiene el siguiente aspecto:

Una fila seleccionada en naranja, con un mensaje de sistema que muestra el nombre de la fila seleccionada

Impedir el parpadeo en diseños personalizados

Android intenta mejorar el rendimiento del desplazamiento de ListView mediante el almacenamiento en caché de la información de diseño. Si tiene listas de datos de desplazamiento datos largas, también debe establecer la propiedad android:cacheColorHint en la declaración en la ListView definición de AXML de la actividad (en el mismo valor de color que el fondo del diseño de fila personalizado). Si no se incluye esta sugerencia, se podría producir un "parpadeo" cuando el usuario se desplaza por una lista con colores de fondo de fila personalizados.