Personalización de la apariencia de las celdas de ListView

La clase Xamarin.FormsListView se usa para presentar listas desplazables, que se pueden personalizar mediante el uso de elementos ViewCell. Un elemento ViewCell puede mostrar texto e imágenes, indicar un estado true/false y recibir la entrada del usuario.

Celdas integradas

Xamarin.Forms incluye celdas integradas que funcionan para muchas aplicaciones:

  • Los controles TextCell se usan para mostrar texto con una segunda línea opcional para el texto detallado.
  • Los controles ImageCell son similares a TextCell, pero incluyen una imagen a la izquierda del texto.
  • Los controles SwitchCell se usan para presentar y capturar los estados activado/desactivado o true/false.
  • Los controles EntryCell se usan para presentar datos de texto que el usuario puede editar.

Los controles SwitchCell y EntryCell se usan con más frecuencia en el contexto de una instancia de TableView.

TextCell

TextCell es una celda para mostrar texto, opcionalmente con una segunda línea como texto de detalle. En la captura de pantalla siguiente se muestran elementos TextCell en iOS y Android:

Ejemplo de TextCell predeterminado

TextCells se representan como controles nativos en tiempo de ejecución, por lo que el rendimiento es muy bueno en comparación con una instancia personalizada de ViewCell. TextCells son personalizables, lo que le permite establecer las siguientes propiedades:

  • Text: el texto que se muestra en la primera línea, en una fuente grande.
  • Detail: el texto que se muestra debajo de la primera línea, en una fuente más pequeña.
  • TextColor: el color del texto.
  • DetailColor: el color del texto de detalle

En la captura de pantalla siguiente se muestran elementos TextCell con propiedades de color personalizadas:

Ejemplo de TextCell personalizado

ImageCell

ImageCell, como TextCell, se puede usar para mostrar texto y texto de detalle secundario, y ofrece un gran rendimiento mediante el uso de los controles nativos de cada plataforma. ImageCell difiere de TextCell en que muestra una imagen a la izquierda del texto.

En la captura de pantalla siguiente se muestran elementos ImageCell en iOS y Android:

ImageCell resulta útil cuando es necesario mostrar una lista de datos con un aspecto visual, como una lista de contactos o películas. Los elemento ImageCell son personalizables, lo que le permite establecer lo siguiente:

  • Text: el texto que se muestra en la primera línea, en una fuente grande
  • Detail: el texto que se muestra debajo de la primera línea, en una fuente más pequeña
  • TextColor: el color del texto
  • DetailColor: el color del texto de detalle
  • ImageSource: la imagen que se va a mostrar junto al texto

En la captura de pantalla siguiente se muestran elementos ImageCell con propiedades de color personalizadas:

Celdas personalizadas

Las celdas personalizadas permiten crear diseños de celda que no son compatibles con las celdas integradas. Por ejemplo, es posible que quiera presentar una celda con dos etiquetas que tengan un peso equivalente. Un elemento TextCell sería insuficiente porque TextCell tiene una etiqueta que es más pequeña. La mayoría de las personalizaciones de celdas agregan datos adicionales de solo lectura (como etiquetas adicionales, imágenes u otra información de visualización).

Todas las celdas personalizadas deben derivar de ViewCell, la misma clase base que usan todos los tipos de celda integrados.

Xamarin.Forms ofrece un comportamiento de almacenamiento en caché en el control ListView que puede mejorar el rendimiento de desplazamiento para algunos tipos de celdas personalizadas.

En la captura de pantalla siguiente se muestra un ejemplo de una celda personalizada:

XAML

La celda personalizada que se muestra en la captura de pantalla anterior se puede crear con el código XAML siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="demoListView.ImageCellPage">
    <ContentPage.Content>
        <ListView  x:Name="listView">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout BackgroundColor="#eee"
                        Orientation="Vertical">
                            <StackLayout Orientation="Horizontal">
                                <Image Source="{Binding image}" />
                                <Label Text="{Binding title}"
                                TextColor="#f35e20" />
                                <Label Text="{Binding subtitle}"
                                HorizontalOptions="EndAndExpand"
                                TextColor="#503026" />
                            </StackLayout>
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </ContentPage.Content>
</ContentPage>

El código XAML funciona de la siguiente manera:

  • La celda personalizada se anida dentro de DataTemplate, que está dentro de ListView.ItemTemplate. Es el mismo proceso que usar cualquier celda integrada.
  • ViewCell es el tipo de la celda personalizada. El elemento secundario del elemento DataTemplate debe ser de la clase ViewCell, o bien derivarse de ella.
  • Dentro de ViewCell, cualquier diseño de Xamarin.Forms puede administrar el diseño. En este ejemplo, el diseño se administra mediante StackLayout, que permite personalizar el color de fondo.

Nota:

Cualquier propiedad de StackLayout que se pueda enlazar se puede enlazar dentro de una celda personalizada. Pero esta funcionalidad no se muestra en el ejemplo de XAML.

Código

Una celda personalizada también se puede crear en el código. En primer lugar, se debe crear una clase personalizada que derive de ViewCell:

public class CustomCell : ViewCell
    {
        public CustomCell()
        {
            //instantiate each of our views
            var image = new Image ();
            StackLayout cellWrapper = new StackLayout ();
            StackLayout horizontalLayout = new StackLayout ();
            Label left = new Label ();
            Label right = new Label ();

            //set bindings
            left.SetBinding (Label.TextProperty, "title");
            right.SetBinding (Label.TextProperty, "subtitle");
            image.SetBinding (Image.SourceProperty, "image");

            //Set properties for desired design
            cellWrapper.BackgroundColor = Color.FromHex ("#eee");
            horizontalLayout.Orientation = StackOrientation.Horizontal;
            right.HorizontalOptions = LayoutOptions.EndAndExpand;
            left.TextColor = Color.FromHex ("#f35e20");
            right.TextColor = Color.FromHex ("503026");

            //add views to the view hierarchy
            horizontalLayout.Children.Add (image);
            horizontalLayout.Children.Add (left);
            horizontalLayout.Children.Add (right);
            cellWrapper.Children.Add (horizontalLayout);
            View = cellWrapper;
        }
    }

En el constructor de la página, la propiedad ItemTemplate de ListView se establece en un objeto DataTemplate con el tipo CustomCell especificado:

public partial class ImageCellPage : ContentPage
    {
        public ImageCellPage ()
        {
            InitializeComponent ();
            listView.ItemTemplate = new DataTemplate (typeof(CustomCell));
        }
    }

Cambios de contexto de enlace

Al enlazar a las instancias de BindableProperty de un tipo de celda personalizado, los controles de interfaz de usuario que muestran los valores BindableProperty deben usar la invalidación OnBindingContextChanged para establecer los datos que se mostrarán en cada celda, en lugar del constructor de celdas, como se muestra en el ejemplo de código siguiente:

public class CustomCell : ViewCell
{
    Label nameLabel, ageLabel, locationLabel;

    public static readonly BindableProperty NameProperty =
        BindableProperty.Create ("Name", typeof(string), typeof(CustomCell), "Name");
    public static readonly BindableProperty AgeProperty =
        BindableProperty.Create ("Age", typeof(int), typeof(CustomCell), 0);
    public static readonly BindableProperty LocationProperty =
        BindableProperty.Create ("Location", typeof(string), typeof(CustomCell), "Location");

    public string Name
    {
        get { return(string)GetValue (NameProperty); }
        set { SetValue (NameProperty, value); }
    }

    public int Age
    {
        get { return(int)GetValue (AgeProperty); }
        set { SetValue (AgeProperty, value); }
    }

    public string Location
    {
        get { return(string)GetValue (LocationProperty); }
        set { SetValue (LocationProperty, value); }
    }
    ...

    protected override void OnBindingContextChanged ()
    {
        base.OnBindingContextChanged ();

        if (BindingContext != null)
        {
            nameLabel.Text = Name;
            ageLabel.Text = Age.ToString ();
            locationLabel.Text = Location;
        }
    }
}

Se llamará a la invalidación de OnBindingContextChanged cuando se desencadene el evento BindingContextChanged, en respuesta al cambio del valor de la propiedad BindingContext. Por tanto, cuando cambia BindingContext, los controles de interfaz de usuario que muestran los valores BindableProperty deben establecer sus datos. Tenga en cuenta que se debe comprobar si BindingContext tiene un valor null, ya que esto se puede establecer mediante Xamarin.Forms para la recolección de elementos no utilizados, lo que a su vez hará que se llame a la invalidación de OnBindingContextChanged.

Como alternativa, los controles de interfaz de usuario se pueden enlazar a las instancias de BindableProperty para mostrar sus valores, lo que elimina la necesidad de invalidar el método OnBindingContextChanged.

Nota:

Al invalidar OnBindingContextChanged, asegúrese de llamar al método OnBindingContextChanged de la clase base para que los delegados registrados reciban el evento BindingContextChanged.

En XAML, el enlace del tipo de celda personalizado a los datos se puede lograr como se muestra en el ejemplo de código siguiente:

<ListView x:Name="listView">
    <ListView.ItemTemplate>
        <DataTemplate>
            <local:CustomCell Name="{Binding Name}" Age="{Binding Age}" Location="{Binding Location}" />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Esto enlaza las propiedades enlazables Name, Age y Location de la instancia de CustomCell, a las propiedades Name, Age y Location de cada objeto de la colección subyacente.

En el siguiente ejemplo de código, se muestra el enlace equivalente en C#:

var customCell = new DataTemplate (typeof(CustomCell));
customCell.SetBinding (CustomCell.NameProperty, "Name");
customCell.SetBinding (CustomCell.AgeProperty, "Age");
customCell.SetBinding (CustomCell.LocationProperty, "Location");

var listView = new ListView
{
    ItemsSource = people,
    ItemTemplate = customCell
};

En iOS y Android, si ListView recicla elementos y la celda personalizada usa un representador personalizado, el representador personalizado debe implementar correctamente la notificación de cambio de propiedad. Cuando se reutilizan las celdas, sus valores de propiedad cambiarán cuando el contexto de enlace se actualice a la de una celda disponible, con los eventos PropertyChanged generados. Para más información, vea Personalización de una instancia de ViewCell. Para más información sobre el reciclaje de celdas, vea Estrategia de almacenamiento en caché.