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:
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:
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 deListActivity
. Puede personalizar filas para cualquierListView
, pero también se pueden incluir otros controles en un diseño deActivity
(como un título, botones u otros elementos de la interfaz de usuario). En este ejemplo se agrega un título encima deListView
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 controlListView
.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 deGetView
.ItemClick
debe declararse de forma diferente (se adjunta un controlador de eventos aListView.ItemClick
, en lugar de una invalidaciónOnListItemClick
enListActivity
).
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í:
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:
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.