ListView

Browse sample. Examina la muestra

.NET Multi-platform App UI (.NET MAUI) ListView muestra una lista vertical desplazable de elementos de datos seleccionables. Aunque ListView administra la apariencia de la lista, la apariencia de cada elemento de la lista se define mediante un DataTemplate que usa Cell para mostrar elementos. .NET MAUI incluye tipos de celda para mostrar combinaciones de texto e imágenes, y también puedes definir celdas personalizadas que muestren cualquier contenido que desees. ListView también incluye compatibilidad para mostrar encabezados y pies de página, datos agrupados, deslizar para actualizar y elementos de menú contextual.

El clase ListView se deriva de la clase ItemsView<Cell>, de la que hereda las propiedades siguientes:

  • ItemsSource, de tipo IEnumerable, especifica la colección de elementos que se mostrarán, y tiene un valor predeterminado de null.
  • ItemTemplate, de tipo DataTemplate, especifica la plantilla que se aplicará a cada elemento de la colección de elementos que se mostrará.

ListView define las siguientes propiedades:

  • Footer, de tipo object, especifica la cadena o vista que se mostrará al final de la lista.
  • FooterTemplate, de tipo DataTemplate, especifica el DataTemplate que se va a usar para dar formato a Footer.
  • GroupHeaderTemplate, de tipo DataTemplate, define el DataTemplate utilizado para definir la apariencia del encabezado de cada grupo. Esta propiedad es mutuamente excluyente con la propiedad GroupDisplayBinding. Por lo tanto, configurar esta propiedad establecerá GroupDisplayBinding en null.
  • HasUnevenRows, de tipo bool, indica si los elementos de la lista pueden tener filas de diferentes altos. El valor predeterminado de esta propiedad es false.
  • Header, de tipo object, especifica la cadena o vista que se mostrará al principio de la lista.
  • HeaderTemplate, de tipo DataTemplate, especifica el DataTemplate que se va a usar para dar formato a Header.
  • HorizontalScrollBarVisibility, de tipo ScrollBarVisibility, indica cuándo estará visible la barra de desplazamiento horizontal.
  • IsGroupedEnabled, de tipo bool, indica si los datos subyacentes deben mostrarse en grupos. El valor predeterminado de esta propiedad es false.
  • IsPullToRefreshEnabled, de tipo bool, indica si el usuario puede deslizar el dedo hacia abajo para hacer que ListView actualice sus datos. El valor predeterminado de esta propiedad es false.
  • IsRefreshing, de tipo bool, indica si el ListView se está actualizando actualmente. El valor predeterminado de esta propiedad es false.
  • RefreshCommand, de tipo ICommand, representa el comando que se ejecutará cuando se desencadene una actualización.
  • RefreshControlColor, de tipo Color, determina el color de la visualización de actualización que se muestra mientras se produce una actualización.
  • RowHeight, de tipo int, determina el alto de cada fila cuando HasUnevenRows es false.
  • SelectedItem, de tipo object, representa el elemento seleccionado actualmente en ListView.
  • SelectionMode, de tipo ListViewSelectionMode, indica si los elementos se pueden seleccionar en ListView o no. El valor predeterminado de esta propiedad es Single.
  • SeparatorColor, de tipo Color, define el color de la barra que separa los elementos de la lista.
  • SeparatorVisibility, de tipo SeparatorVisibility, define si los separadores son visibles entre los elementos.
  • VerticalScrollBarVisibility, de tipo ScrollBarVisibility, indica cuándo estará visible la barra de desplazamiento vertical.

Todas estas propiedades están respaldadas por objetos BindableProperty, lo que significa que pueden ser destinos de enlaces de datos y estilo.

Además, ListView define las siguientes propiedades que no están respaldadas por objetos BindableProperty:

  • GroupDisplayBinding, de tipo BindingBase, el enlace que se usará para mostrar el encabezado de grupo. Esta propiedad es mutuamente excluyente con la propiedad GroupHeaderTemplate. Por lo tanto, configurar esta propiedad establecerá GroupHeaderTemplate en null.
  • GroupShortNameBinding, de tipo BindingBase, el enlace del nombre que se va a mostrar en listas de accesos directos agrupados.
  • CachingStrategy, de tipo ListViewCachingStrategy, define la estrategia de reutilización de celdas de ListView. Se trata de una propiedad de solo lectura.

ListView define los siguientes eventos:

  • ItemAppearing, que se produce cuando la representación visual de un elemento se agrega al diseño visual de ListView. El objeto ItemVisibilityEventArgs que acompaña a este evento define las propiedades Item y Index:
  • ItemDisappearing, que se produce cuando la representación visual de un elemento se quita del diseño visual de ListView. El objeto ItemVisibilityEventArgs que acompaña a este evento define las propiedades Item y Index.
  • ItemSelected, que se genera cuando se selecciona un nuevo elemento de la lista. El objeto SelectedItemChangedEventArgs que acompaña a este evento define las propiedades SelectedItem y SelectedItemIndex.
  • ItemTapped, que se genera cuando se pulsa un elemento en ListView. El objeto ItemTappedEventArgs que acompaña a este evento define las propiedades Group, Item y ItemIndex.
  • Refreshing, que se genera cuando se desencadena una operación de deslizar para actualizar en ListView.
  • Scrolled, . El objeto ScrolledEventArgs que acompaña a este evento define las propiedades ScrollX y ScrollY.
  • ScrollToRequested. El objeto ScrollToRequestedEventArgs que acompaña a este evento define las propiedades Element, Mode, Position, ScrollX, ScrollY y ShouldAnimate.

Relleno de ListView con datos

ListView se rellena con datos estableciendo su propiedad ItemsSource en cualquier colección que implemente IEnumerable.

Importante

Si se necesita ListView para actualizar a medida que se agregan, quitan o cambian elementos en la colección subyacente, la colección subyacente debe ser una colección IEnumerable que envíe notificaciones de cambio de propiedad, como ObservableCollection.

ListView se puede rellenar con datos mediante el enlace de datos para enlazar su propiedad ItemsSource a una colección IEnumerable. En XAML, esto se consigue con la extensión de marcado Binding.

<ListView ItemsSource="{Binding Monkeys}" />

El código de C# equivalente es el siguiente:

ListView listView = new ListView();
listView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");

En este ejemplo, los datos de la propiedad ItemsSource se enlazan a la propiedad Monkeys del modelo de vista conectado.

Nota:

Los enlaces compilados se pueden habilitar para mejorar el rendimiento del enlace de datos en aplicaciones .NET MAUI. Para obtener más información, consulta Enlaces compilados.

Para obtener más información sobre el enlace de datos, consulta Enlace de datos.

Definición de la apariencia de elementos

Se puede definir la apariencia de cada elemento en ListView mediante el establecimiento de la propiedad ItemTemplate en DataTemplate:

<ListView ItemsSource="{Binding Monkeys}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <Grid Padding="10">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <Image Grid.RowSpan="2"
                           Source="{Binding ImageUrl}"
                           Aspect="AspectFill"
                           HeightRequest="60"
                           WidthRequest="60" />
                    <Label Grid.Column="1"
                           Text="{Binding Name}"
                           FontAttributes="Bold" />
                    <Label Grid.Row="1"
                           Grid.Column="1"
                           Text="{Binding Location}"
                           FontAttributes="Italic"
                           VerticalOptions="End" />
                </Grid>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Los elementos especificados en DataTemplate definen la apariencia de cada elemento de la lista y el elemento secundario de DataTemplate debe ser un objeto Cell. En este ejemplo, el diseño dentro de DataTemplate se administra mediante Grid. Grid contiene un objeto Image y dos objetos Label, que se enlazan a las propiedades de la clase Monkey:

public class Monkey
{
    public string Name { get; set; }
    public string Location { get; set; }
    public string Details { get; set; }
    public string ImageUrl { get; set; }
}

En la captura de pantalla siguiente se muestra el resultado de crear plantillas para cada elemento de la lista:

Screenshot of ListView where each item is templated.

Para obtener más información sobre las plantillas de datos, consulta Plantillas de datos.

Celdas

La apariencia de cada elemento de ListView se define mediante DataTemplate, y DataTemplate debe hacer referencia a una clase Cell para mostrar elementos. Cada celda representa un elemento de datos en ListView. .NET MAUI incluye las siguientes celdas integradas:

  • TextCell, que muestra el texto primario y secundario en líneas diferentes.
  • ImageCell, que muestra una imagen con texto primario y secundario en líneas diferentes.
  • SwitchCell, que muestra texto y un conmutador que se puede activar o desactivar.
  • EntryCell, que muestra una etiqueta y un texto que se pueden editar.
  • ViewCell, que es una celda personalizada cuya apariencia se define mediante View. Este tipo de celda debe usarse cuando desees definir completamente la apariencia de cada elemento de un ListView.

Normalmente, SwitchCell y EntryCell solo se usarán en TableView y no se usarán en ListView. Para obtener más información sobre SwitchCell y EntryCell, consulta TableView.

Celda de texto

TextCell muestra un texto primario y secundario en líneas diferentes. TextCell define las siguientes propiedades:

  • Text, de tipo string, define el texto primario que se va a mostrar.
  • TextColor, de tipo Color, representa el color del texto primario.
  • Detail, de tipo string, define el texto secundario que se va a mostrar.
  • DetailColor, de tipo Color, indica el color del texto secundario.
  • Command, de tipo ICommand, define el comando que se ejecuta cuando se pulsa la celda.
  • CommandParameter, de tipo object, representa el parámetro que se pasa al comando.

Estas propiedades están respaldadas por objetos BindableProperty, lo que significa que pueden ser destinos de los enlaces de datos, y con estilo.

En el ejemplo siguiente se muestra cómo usar TextCell para definir la apariencia de elementos en ListView:

<ListView ItemsSource="{Binding Food}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextCell Text="{Binding Name}"
                      Detail="{Binding Description}" />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

En la captura de pantalla siguiente se muestra la apariencia resultante de la celda:

Screenshot of ListView where each item is a TextCell.

Celda de imagen

ImageCell muestra una imagen con texto principal y secundario en líneas independientes. ImageCell hereda las propiedades de TextCell y define la propiedad ImageSource, de tipo ImageSource, que especifica la imagen que se va a mostrar en la celda. Estas propiedades están respaldadas por objetos BindableProperty, lo que significa que pueden ser destinos de los enlaces de datos, y recibir formato de estilo.

En el ejemplo siguiente se muestra cómo usar la apariencia de ImageCell para definir el aspecto de los elementos en ListView:

<ListView ItemsSource="{Binding Food}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ImageCell ImageSource="{Binding Image}"
                       Text="{Binding Name}"
                       Detail="{Binding Description}" />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

En la captura de pantalla siguiente se muestra la apariencia resultante de la celda:

Screenshot of ListView where each item is an ImageCell.

Celda de vista

ViewCell es una celda personalizada cuya apariencia se define mediante View. ViewCell define una propiedad View, de tipo View, que define la vista que representa el contenido de la celda. Estas propiedades están respaldadas por objetos BindableProperty, lo que significa que pueden ser destinos de los enlaces de datos, y recibir formato de estilo.

Nota:

La propiedad View es la propiedad de contenido de la clase ViewCell y, por tanto, no es necesario establecerla explícitamente desde XAML.

En el ejemplo siguiente se muestra cómo usar ViewCell para definir la apariencia de elementos en ListView:

<ListView ItemsSource="{Binding Monkeys}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <Grid Padding="10">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <Image Grid.RowSpan="2"
                           Source="{Binding ImageUrl}"
                           Aspect="AspectFill"
                           HeightRequest="60"
                           WidthRequest="60" />
                    <Label Grid.Column="1"
                           Text="{Binding Name}"
                           FontAttributes="Bold" />
                    <Label Grid.Row="1"
                           Grid.Column="1"
                           Text="{Binding Location}"
                           FontAttributes="Italic"
                           VerticalOptions="End" />
                </Grid>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Dentro de ViewCell, el diseño se puede administrar mediante cualquier diseño de .NET MAUI. En este ejemplo, el diseño se administra mediante Grid. Grid contiene un objeto Image y dos objetos Label, que se enlazan todos a las propiedades de la clase Monkey.

En la captura de pantalla siguiente se muestra el resultado de crear plantillas para cada elemento de la lista:

Screenshot of ListView where each item is templated with a ViewCell.

Elección de la apariencia del elemento en tiempo de ejecución

La apariencia de cada elemento de ListView se puede elegir en tiempo de ejecución, en función del valor del elemento, estableciendo la propiedad ItemTemplate en un objeto DataTemplateSelector:

<ContentPage ...
             xmlns:templates="clr-namespace:ListViewDemos.Templates">
    <ContentPage.Resources>
        <DataTemplate x:Key="AmericanMonkeyTemplate">
            <ViewCell>
                ...
            </ViewCell>
        </DataTemplate>

        <DataTemplate x:Key="OtherMonkeyTemplate">
            <ViewCell>
                ...
            </ViewCell>
        </DataTemplate>

        <templates:MonkeyDataTemplateSelector x:Key="MonkeySelector"
                                             AmericanMonkey="{StaticResource AmericanMonkeyTemplate}"
                                             OtherMonkey="{StaticResource OtherMonkeyTemplate}" />
    </ContentPage.Resources>

    <StackLayout Margin="20">
        <ListView ItemsSource="{Binding Monkeys}"
                  ItemTemplate="{StaticResource MonkeySelector}" />
    </StackLayout>
</ContentPage>

La propiedad ItemTemplate se establece en un objeto MonkeyDataTemplateSelector. En el ejemplo siguiente se muestra la clase MonkeyDataTemplateSelector:

public class MonkeyDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate AmericanMonkey { get; set; }
    public DataTemplate OtherMonkey { get; set; }

    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    {
        return ((Monkey)item).Location.Contains("America") ? AmericanMonkey : OtherMonkey;
    }
}

La clase MonkeyDataTemplateSelector define las propiedades AmericanMonkey y OtherMonkeyDataTemplate que se establecen en plantillas de datos diferentes. La invalidación de OnSelectTemplate devuelve la plantilla AmericanMonkey, que muestra el nombre de mono y la ubicación en verde azulado, cuando el nombre de mono contiene "America". Cuando el nombre de mono no contiene "America", la invalidación de OnSelectTemplate devuelve la plantilla OtherMonkey, que muestra el nombre de mono y la ubicación en plateado:

Screenshot of ListView runtime item template selection.

Para más información sobre los selectores de plantillas de datos, consulta Creación de DataTemplateSelector.

Respuesta a la selección de elementos

De manera predeterminada, la selección ListView está habilitada. Pero este comportamiento se puede cambiar modificando la propiedad SelectionMode. La enumeración ListViewSelectionMode define los miembros siguientes:

  • None: indica que no se puede seleccionar ningún elemento.
  • Single: indica que se puede seleccionar un solo elemento, resaltando el elemento seleccionado. Este es el valor predeterminado.

ListView define un evento ItemSelected que se genera cuando cambia la propiedad SelectedItem, ya sea debido a que el usuario selecciona un elemento de la lista o cuando una aplicación establece la propiedad. El objeto SelectedItemChangedEventArgs que acompaña a este evento tiene las propiedades SelectedItem y SelectedItemIndex.

Cuando la propiedad SelectionMode se establece en Single, se puede seleccionar un solo elemento en ListView. Cuando un elemento está seleccionado, la propiedad SelectedItem se establece en el valor del elemento seleccionado. Cuando la propiedad cambia, se genera el evento ItemSelected.

En el ejemplo siguiente se muestra ListView que puede responder a la selección de un solo elemento:

<ListView ItemsSource="{Binding Monkeys}"
          ItemSelected="OnItemSelected">
    ...
</ListView>

En este ejemplo, el controlador de eventos OnItemSelected se ejecuta cuando se desencadena el evento ItemSelected y el controlador de eventos recupera el elemento seleccionado:

void OnItemSelected(object sender, SelectedItemChangedEventArgs args)
{
    Monkey item = args.SelectedItem as Monkey;
}

En la captura de pantalla siguiente se muestra la selección de un solo elemento en ListView:

Screenshot of a ListView with a selection.

Borrar la selección

La propiedad SelectedItem se puede borrar estableciéndola o estableciendo el objeto al que se enlaza, en null.

Deshabilitación de la selección

La selección ListView está habilitada de manera predeterminada. Pero se puede deshabilitar estableciendo la propiedad SelectionMode en None:

<ListView ...
          SelectionMode="None" />

Cuando la propiedad SelectionMode está establecida en None, los elementos de ListView no se pueden seleccionar, la propiedad SelectedItem permanecerá como null y el evento ItemSelected no se desencadenará.

Datos de caché

ListView es una vista eficaz para mostrar datos, pero tiene algunas limitaciones. El rendimiento del desplazamiento puede sufrir al usar celdas personalizadas, especialmente cuando contienen jerarquías de vistas profundamente anidadas o usan determinados diseños que requieren una medición compleja. Afortunadamente, hay técnicas que puedes usar para evitar un rendimiento deficiente.

A menudo ListView se usa para mostrar muchos más datos de los que caben en la pantalla. Por ejemplo, una aplicación de música podría tener una biblioteca de canciones con miles de entradas. La creación de un elemento para cada entrada desperdiciaría memoria valiosa y el funcionamiento no sería bueno. La creación y destrucción de filas constantemente requeriría que la aplicación cree instancias y limpie objetos constantemente, lo que también funcionaría mal.

Para conservar la memoria, los equivalentes ListView nativos de cada plataforma tienen características integradas para reutilizar filas. Solo las celdas visibles en pantalla se cargan en la memoria y el contenido se carga en celdas existentes. Este patrón impide que la aplicación cree instancias de miles de objetos, lo que ahorra tiempo y memoria.

.NET MAUI permite la reutilización de celdas ListView mediante la enumeración ListViewCachingStrategy, que define los siguientes miembros:

  • RetainElement, especifica que ListView generará una celda para cada elemento de la lista.
  • RecycleElement, especifica que ListView intentará minimizar su superficie de memoria y velocidad de ejecución mediante el reciclaje de las celdas de la lista.
  • RecycleElementAndDataTemplate, como RecycleElement, al mismo tiempo que garantiza que cuando ListView usa un DataTemplateSelector, los objetos DataTemplate se almacenan en caché por el tipo de elemento de la lista.

Conservar elementos

La estrategia de almacenamiento en caché RetainElement especifica que ListView generará una celda para cada elemento de la lista y es el comportamiento predeterminado de ListView. Debe usarse en las siguientes circunstancias:

  • Cada celda tiene un gran número de enlaces (de 20 a 30+).
  • La plantilla de celda cambia con frecuencia.
  • Las pruebas revelan que la estrategia de almacenamiento en caché RecycleElement da como resultado una velocidad de ejecución reducida.

Es importante reconocer las consecuencias de la estrategia de almacenamiento en caché RetainElement al trabajar con celdas personalizadas. Cualquier código de inicialización de celda tendrá que ejecutarse para cada creación de celda, que puede ser varias veces por segundo. En esta circunstancia, las técnicas de diseño que estaban bien en una página, como el uso de varios objetos StackLayout anidados, se convierten en cuellos de botella de rendimiento cuando se configuran y destruyen en tiempo real a medida que el usuario se desplaza.

Elementos de reciclaje

La estrategia de almacenamiento en caché RecycleElement especifica que ListView intentará minimizar la superficie de memoria y la velocidad de ejecución mediante el reciclaje de las celdas de la lista. Este modo no siempre ofrece una mejora del rendimiento y se deben realizar pruebas para determinar cualquier mejora. Pero es la opción preferida y debe usarse en las siguientes circunstancias:

  • Cada celda tiene un número pequeño a moderado de enlaces.
  • Cada celda BindingContext define todos los datos de celda.
  • Cada celda es en gran medida similar, con la plantilla de celda sin cambiar.

Durante la virtualización, la celda tendrá su contexto de enlace actualizado, por lo que si una aplicación usa este modo, debe asegurarse de que las actualizaciones de contexto de enlace se controlan correctamente. Todos los datos sobre la celda deben provenir del contexto de enlace o se pueden producir errores de coherencia. Este problema se puede evitar usando el enlace de datos para mostrar datos de celda. Como alternativa, los datos de celda deben establecerse en la invalidación OnBindingContextChanged, en lugar de en el constructor de la celda personalizada, como se muestra en el ejemplo siguiente:

public class CustomCell : ViewCell
{
    Image image = null;

    public CustomCell()
    {
        image = new Image();
        View = image;
    }

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

        var item = BindingContext as ImageItem;
        if (item != null)
        {
            image.Source = item.ImageUrl;
        }
    }
}

Elementos de reciclaje con DataTemplateSelector

Cuando un ListView usa DataTemplateSelector para seleccionar un DataTemplate, la estrategia de almacenamiento en caché RecycleElement no almacena en caché objetos DataTemplate. En su lugar, se selecciona un DataTemplate para cada elemento de datos de la lista.

Nota:

La estrategia de almacenamiento en caché RecycleElement requiere que cuando se pida a DataTemplateSelector que seleccione un DataTemplate que cada DataTemplate debe devolver el mismo tipo ViewCell. Por ejemplo, dado un ListView con un DataTemplateSelector que puede devolver MyDataTemplateA (donde MyDataTemplateA devuelve un ViewCell de tipo MyViewCellA), o MyDataTemplateB (donde MyDataTemplateB devuelve un ViewCell de tipo MyViewCellB), cuando MyDataTemplateA se devuelve, se debe devolver MyViewCellA o se producirá una excepción.

Elementos de reciclaje con DataTemplates

La estrategia de almacenamiento en caché RecycleElementAndDataTemplate se basa en la estrategia de almacenamiento en caché RecycleElement, asegurándose además de que cuando un ListView usa un DataTemplateSelector para seleccionar un DataTemplate, los objetos DataTemplate se almacenan en caché por el tipo de elemento de la lista. Por lo tanto, los objetos DataTemplate se seleccionan una vez por tipo de elemento, en lugar de una vez por instancia de elemento.

Nota:

La estrategia de almacenamiento en caché RecycleElementAndDataTemplate requiere que los objetos DataTemplate devueltos por DataTemplateSelector deben usar el constructor DataTemplate que toma un Type.

Establecer la estrategia de almacenamiento en caché

La estrategia de almacenamiento en caché ListView se puede definir mediante en XAML estableciendo el atributo CachingStrategy:

<ListView CachingStrategy="RecycleElement">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
              ...
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

En C#, la estrategia de almacenamiento en caché se establece a través de una sobrecarga de constructor:

ListView listView = new ListView(ListViewCachingStrategy.RecycleElement);

Establecer la estrategia de almacenamiento en caché en una ListView subclasada

Establecer el atributo CachingStrategy de XAML en una subclase ListView no generará el comportamiento deseado, ya que no hay ninguna propiedad CachingStrategy en ListView. La solución a este problema consiste en especificar un constructor en la clase con subclases ListView que acepta un parámetro ListViewCachingStrategy y lo pasa a la clase base:

public class CustomListView : ListView
{
    public CustomListView (ListViewCachingStrategy strategy) : base (strategy)
    {
    }
    ...
}

Después, el valor de enumeración ListViewCachingStrategy se puede especificar desde XAML mediante el atributo x:Arguments:

<local:CustomListView>
    <x:Arguments>
        <ListViewCachingStrategy>RecycleElement</ListViewCachingStrategy>
    </x:Arguments>
</local:CustomListView>

Encabezados y pies de página

ListView puede presentar un encabezado y pie de página que se desplazan con los elementos de la lista. El encabezado y el pie de página pueden ser cadenas, vistas u objetos DataTemplate.

ListView define las siguientes propiedades para especificar el encabezado y el pie de página:

  • Header, de tipo object, especifica la cadena, el enlace o la vista que se mostrarán al principio de la lista.
  • HeaderTemplate, de tipo DataTemplate, especifica el DataTemplate que se va a usar para dar formato a Header.
  • Footer, de tipo object, especifica la cadena, el enlace o la vista que se mostrarán al final de la lista.
  • FooterTemplate, de tipo DataTemplate, especifica el DataTemplate que se va a usar para dar formato a Footer.

Todas estas propiedades están respaldadas por objetos BindableProperty, lo que significa que las propiedades pueden ser destinos de los enlaces de datos.

La propiedad Header y Footer se puede establecer en valores string, como se muestra en el siguiente ejemplo:

<ListView ItemsSource="{Binding Monkeys}"
          Header="Monkeys"
          Footer="2022">
    ...
</ListView>

En la captura de pantalla siguiente se muestra el encabezado resultante:

Screenshot of a ListView string header.

Las propiedades Header y Footer se pueden establecer en una vista. Esto puede ser una sola vista o una vista que contenga varias vistas secundarias. En el siguiente ejemplo, se muestran las propiedades Header y Footer, cada una establecida en un objeto StackLayout que contiene un objeto Label:

<ListView ItemsSource="{Binding Monkeys}">
    <ListView.Header>
        <StackLayout BackgroundColor="LightGray">
            <Label Margin="10,0,0,0"
                   Text="Monkeys"
                   FontSize="12"
                   FontAttributes="Bold" />
        </StackLayout>
    </ListView.Header>
    <ListView.Footer>
        <StackLayout BackgroundColor="LightGray">
            <Label Margin="10,0,0,0"
                   Text="Friends of Monkey"
                   FontSize="12"
                   FontAttributes="Bold" />
        </StackLayout>
    </ListView.Footer>
    ...
</ListView>

En la captura de pantalla siguiente se muestra el encabezado resultante:

Screenshot of CollectionView header and footer using views.

Las propiedades HeaderTemplate y FooterTemplate se pueden establecer en objetos DataTemplate que se usan para dar formato al encabezado y al pie de página. En este escenario, las propiedades Header y Footer deben enlazarse al origen actual de la plantilla que se va a aplicar, como se muestra en el siguiente ejemplo:

<ListView ItemsSource="{Binding Monkeys}"
          Header="{Binding .}"
          Footer="{Binding .}">
    <ListView.HeaderTemplate>
        <DataTemplate>
            <StackLayout BackgroundColor="LightGray">
                <Label Margin="10,0,0,0"
                       Text="Monkeys"
                       FontSize="12"
                       FontAttributes="Bold" />
            </StackLayout>
        </DataTemplate>
    </ListView.HeaderTemplate>
    <ListView.FooterTemplate>
        <DataTemplate>
            <StackLayout BackgroundColor="LightGray">
                <Label Margin="10,0,0,0"
                       Text="Friends of Monkey"
                       FontSize="12"
                       FontAttributes="Bold" />
            </StackLayout>
        </DataTemplate>
    </ListView.FooterTemplate>
    ...
</ListView>

Separadores de elementos de control

De manera predeterminada, los separadores se muestran entre los elementos ListView en iOS y Android. Este comportamiento se puede modificar estableciendo la propiedad SeparatorVisibility, de tipo SeparatorVisibility, en None:

<ListView ...
          SeparatorVisibility="None" />

Además, cuando el separador está habilitado, se puede establecer el color con la propiedad SeparatorColor:

<ListView ...
          SeparatorColor="Blue" />

Tamaño de los elementos

De manera predeterminada, todos los elementos de un ListView tienen el mismo alto, que se deriva del contenido del DataTemplate que define la apariencia de cada elemento. Pero este comportamiento se puede cambiar con las propiedades HasUnevenRows y RowHeight. De manera predeterminada, la propiedad HasUnevenRows es false.

La propiedad RowHeight se puede establecer en un int que representa el alto de cada elemento de ListView, siempre que HasUnevenRows sea false. Cuando HasUnevenRows se establece en true, cada elemento de ListView puede tener un alto diferente. El alto de cada elemento se derivará del contenido del DataTemplate del elemento, por lo que cada elemento se ajustará a su contenido.

El tamaño de cada uno de los elementos ListView se puede cambiar mediante programación en runtime cambiando las propiedades del elemento relacionadas con el diseño dentro de DataTemplate, siempre que la propiedad HasUnevenRows sea true. En el ejemplo siguiente se cambia el alto de un objeto Image cuando se pulsa:

void OnImageTapped(object sender, EventArgs args)
{
    Image image = sender as Image;
    ViewCell viewCell = image.Parent.Parent as ViewCell;

    if (image.HeightRequest < 250)
    {
      image.HeightRequest = image.Height + 100;
      viewCell.ForceUpdateSize();
    }
}

En este ejemplo, el controlador de eventos OnImageTapped se ejecuta en respuesta a un objeto Image que se está pulsando. El controlador de eventos actualiza el alto de Image y el método Cell.ForceUpdateSize actualiza el tamaño de la celda, incluso cuando no está visible actualmente.

Advertencia

El uso excesivo del ajuste de tamaño dinámico de los elementos puede provocar una degradación del rendimiento de ListView.

Diseño de derecha a izquierda

ListView puede diseñar su contenido en una dirección de flujo de derecha a izquierda estableciendo su propiedad FlowDirection en RightToLeft. Sin embargo, la propiedad FlowDirection debe establecerse idealmente en una página o diseño raíz, lo que hace que todos los elementos de la página, o del diseño raíz, respondan a la dirección del flujo:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ListViewDemos.RightToLeftListPage"
             Title="Right to left list"
             FlowDirection="RightToLeft">
    <StackLayout Margin="20">
        <ListView ItemsSource="{Binding Monkeys}">
            ...
        </ListView>
    </StackLayout>
</ContentPage>

El valor predeterminado FlowDirection de un elemento con un elemento primario es MatchParent. Por lo tanto, ListView hereda el valor de la propiedad FlowDirection de StackLayout, que a su vez hereda el valor de la propiedad FlowDirection de ContentPage.

Visualización de datos agrupados

Los conjuntos de datos de gran tamaño se pueden volver difíciles de manejar cuando se presentan en una lista de desplazamiento continuo. En este escenario, la agrupación de los datos puede mejorar la experiencia del usuario al facilitar la navegación.

Los datos se deben agrupar antes de que se puedan mostrar. Esto se puede lograr mediante la creación de una lista de grupos, donde cada grupo es una lista de elementos. La lista de grupos debe ser una colección de IEnumerable<T>, donde T define dos fragmentos de datos:

  • Un nombre de grupo.
  • Una colección IEnumerable que define los elementos que pertenecen al grupo.

El proceso de agrupación de datos, por lo tanto, consiste en:

  • Crear un tipo que modele un solo elemento.
  • Crear un tipo que modele un único grupo de elementos.
  • Crear una colección IEnumerable<T>, donde T es el tipo que modela un único grupo de elementos. Esta colección es una colección de grupos, que almacena los datos agrupados.
  • Agregar datos a la colección IEnumerable<T>.

Ejemplo

Al agrupar datos, el primer paso es crear un tipo que modele un solo elemento. En el ejemplo siguiente se muestra la clase Animal:

public class Animal
{
    public string Name { get; set; }
    public string Location { get; set; }
    public string Details { get; set; }
    public string ImageUrl { get; set; }
}

La clase Animal modela un solo elemento. Luego se puede crear un tipo que modele un grupo de elementos. En el ejemplo siguiente se muestra la clase AnimalGroup:

public class AnimalGroup : List<Animal>
{
    public string Name { get; private set; }

    public AnimalGroup(string name, List<Animal> animals) : base(animals)
    {
        Name = name;
    }
}

La clase AnimalGroup hereda de la clase List<T> y agrega una propiedad Name que representa el nombre del grupo.

Después, se puede crear una colección de grupos IEnumerable<T>:

public List<AnimalGroup> Animals { get; private set; } = new List<AnimalGroup>();

Este código define una colección llamada Animals, donde cada elemento de la colección es un objeto AnimalGroup. Cada objeto AnimalGroup consta de un nombre y una colección List<Animal> que define los objetos del grupo Animal.

Después los datos agrupados se pueden agregar a la colección Animals:

Animals.Add(new AnimalGroup("Bears", new List<Animal>
{
    new Animal
    {
        Name = "American Black Bear",
        Location = "North America",
        Details = "Details about the bear go here.",
        ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/0/08/01_Schwarzbär.jpg"
    },
    new Animal
    {
        Name = "Asian Black Bear",
        Location = "Asia",
        Details = "Details about the bear go here.",
        ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Ursus_thibetanus_3_%28Wroclaw_zoo%29.JPG/180px-Ursus_thibetanus_3_%28Wroclaw_zoo%29.JPG"
    },
    // ...
}));

Animals.Add(new AnimalGroup("Monkeys", new List<Animal>
{
    new Animal
    {
        Name = "Baboon",
        Location = "Africa & Asia",
        Details = "Details about the monkey go here.",
        ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Papio_anubis_%28Serengeti%2C_2009%29.jpg/200px-Papio_anubis_%28Serengeti%2C_2009%29.jpg"
    },
    new Animal
    {
        Name = "Capuchin Monkey",
        Location = "Central & South America",
        Details = "Details about the monkey go here.",
        ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Capuchin_Costa_Rica.jpg/200px-Capuchin_Costa_Rica.jpg"
    },
    new Animal
    {
        Name = "Blue Monkey",
        Location = "Central and East Africa",
        Details = "Details about the monkey go here.",
        ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/BlueMonkey.jpg/220px-BlueMonkey.jpg"
    },
    // ...
}));

Este código crea dos grupos en la colección Animals. El primer grupo AnimalGroup se llama Bears y contiene una colección List<Animal> de detalles del portador. El segundo AnimalGroup se llama Monkeys y contiene una colección List<Animal> de detalles de mono.

ListView mostrará los datos agrupados, siempre que los datos se hayan agrupado correctamente, estableciendo la propiedad IsGroupingEnabled en true:

<ListView ItemsSource="{Binding Animals}"
          IsGroupingEnabled="True">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <Grid Padding="10">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <Image Grid.RowSpan="2"
                           Source="{Binding ImageUrl}"
                           Aspect="AspectFill"
                           HeightRequest="60"
                           WidthRequest="60" />
                    <Label Grid.Column="1"
                           Text="{Binding Name}"
                           FontAttributes="Bold" />
                    <Label Grid.Row="1"
                           Grid.Column="1"
                           Text="{Binding Location}"
                           FontAttributes="Italic"
                           VerticalOptions="End" />
                </Grid>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

El código de C# equivalente es el siguiente:

ListView listView = new ListView
{
    IsGroupingEnabled = true
};
listView.SetBinding(ItemsView.ItemsSourceProperty, "Animals");
// ...

La apariencia de cada elemento en ListView se define estableciendo su propiedad ItemTemplate en DataTemplate. Para obtener más información, consulta Definir la apariencia del elemento.

En la captura de pantalla siguiente muestra ListView mostrando datos agrupados:

Screenshot of grouped data in a ListView.

Nota:

De forma predeterminada, ListView mostrará el nombre del grupo en el encabezado del grupo. Este comportamiento se puede cambiar personalizando el encabezado de grupo.

Personalización del encabezado de grupo

Se puede personalizar la apariencia de cada encabezado de grupo estableciendo la propiedad ListView.GroupHeaderTemplate en un objeto DataTemplate:

<ListView ItemsSource="{Binding Animals}"
          IsGroupingEnabled="True">
    <ListView.GroupHeaderTemplate>
        <DataTemplate>
            <ViewCell>
                <Label Text="{Binding Name}"
                       BackgroundColor="LightGray"
                       FontSize="18"
                       FontAttributes="Bold" />
            </ViewCell>
        </DataTemplate>
    </ListView.GroupHeaderTemplate>
    ...
</ListView>

En este ejemplo, cada encabezado de grupo se establece en Label que muestra el nombre del grupo y que tiene otras propiedades de apariencia establecidas. En la captura de pantalla siguiente se muestra el encabezado de grupo personalizado:

Screenshot of a customized group header in a ListView.

Importante

La propiedad GroupHeaderTemplate se excluye mutuamente con la propiedad GroupDisplayBinding. Por lo tanto, no se deben establecer ambas propiedades.

Agrupar sin plantillas

ListView puede mostrar datos agrupados correctamente sin establecer la propiedad ItemTemplate en DataTemplate:

<ListView ItemsSource="{Binding Animals}"
          IsGroupingEnabled="true" />

En este escenario, se pueden mostrar datos significativos reemplazando el método ToString en el tipo que modela un solo elemento y el tipo que modela un único grupo de elementos.

Control del desplazamiento

ListView define dos métodos ScrollTo, que desplazan los elementos en la vista. Una de las sobrecargas desplaza el elemento especificado en la vista, mientras que el otro desplaza el elemento especificado en el grupo especificado en la vista. Ambas sobrecargas tienen argumentos adicionales que permiten especificar la posición exacta del elemento después de que se haya completado el desplazamiento y si se va a animar el desplazamiento.

ListView define un evento ScrollToRequested que se desencadena cuando se invoca uno de los métodos ScrollTo. El objeto ScrollToRequestedEventArgs que acompaña al evento ScrollToRequested tiene muchas propiedades, incluidas ShouldAnimate, Element, Mode y Position. Algunas de estas propiedades se establecen a partir de los argumentos especificados en las llamadas del método ScrollTo.

Además, ListView define un evento Scrolled que se desencadena para indicar que se ha producido el desplazamiento. El objeto ScrolledEventArgs que acompaña al evento Scrolled tiene propiedades ScrollX y ScrollY.

Detectar desplazamiento

ListView define un evento Scrolled que se desencadena para indicar que se produjo desplazamiento. La clase ItemsViewScrolledEventArgs, que representa al objeto que acompaña al evento Scrolled, define las siguientes propiedades:

  • ScrollX, de tipo double, representa la posición X del desplazamiento.
  • ScrollY, de tipo double, representa la posición Y del desplazamiento.

En el ejemplo de XAML siguiente se muestra ListView, que establece un controlador para el evento Scrolled:

<ListView Scrolled="OnListViewScrolled">
    ...
</ListView>

El código de C# equivalente es el siguiente:

ListView listView = new ListView();
listView.Scrolled += OnListViewScrolled;

En este ejemplo de código, el controlador de eventos OnListViewScrolled se ejecuta cuando se desencadena el evento Scrolled:

void OnListViewScrolled(object sender, ScrolledEventArgs e)
{
    // Custom logic
}

Importante

El evento Scrolled se desencadena para los desplazamientos iniciados por el usuario y para los desplazamientos mediante programación.

Desplazar un elemento a la vista

El ScrollTo método desplaza el elemento especificado a la vista. Dado un objeto ListView denominado listView, en el ejemplo siguiente se muestra cómo desplazar el elemento Proboscis Monkey a la vista:

MonkeysViewModel viewModel = BindingContext as MonkeysViewModel;
Monkey monkey = viewModel.Monkeys.FirstOrDefault(m => m.Name == "Proboscis Monkey");
listView.ScrollTo(monkey, ScrollToPosition.MakeVisible, true);

Como alternativa, un elemento de los datos agrupados se puede desplazar a la vista especificando el elemento y el grupo. En el ejemplo siguiente se muestra cómo desplazar el elemento Proboscis Monkey en el grupo Monkeys a la vista:

GroupedAnimalsViewModel viewModel = BindingContext as GroupedAnimalsViewModel;
AnimalGroup group = viewModel.Animals.FirstOrDefault(a => a.Name == "Monkeys");
Animal monkey = group.FirstOrDefault(m => m.Name == "Proboscis Monkey");
listView.ScrollTo(monkey, group, ScrollToPosition.MakeVisible, true);

Nota:

El evento ScrollToRequested se desencadena cuando se invoca el método ScrollTo.

Deshabilitar la animación de desplazamiento

Una animación de desplazamiento se muestra al desplazar un elemento a la vista. Pero esta animación se puede deshabilitar estableciendo el argumento animated del método ScrollTo en false:

listView.ScrollTo(monkey, position: ScrollToPosition.MakeVisible, animate: false);

Posición de desplazamiento del control

Al desplazar un elemento a la vista, se puede especificar la posición exacta del elemento después de que se haya completado el desplazamiento con el argumento position de los métodos ScrollTo. Este argumento acepta un miembro de enumeración ScrollToPosition.

MakeVisible

El miembro ScrollToPosition.MakeVisible indica que el elemento debe desplazarse hasta que esté visible en la vista:

listView.ScrollTo(monkey, position: ScrollToPosition.MakeVisible, animate: true);

Start

El miembro ScrollToPosition.Start indica que el elemento debe desplazarse hasta el inicio de la vista:

listView.ScrollTo(monkey, position: ScrollToPosition.Start, animate: true);

Centrar

El miembro ScrollToPosition.Center indica que el elemento debe desplazarse al centro de la vista:

listView.ScrollTo(monkey, position: ScrollToPosition.Center, animate: true);

Fin

El miembro ScrollToPosition.End indica que el elemento debe desplazarse al final de la vista:

listView.ScrollTo(monkey, position: ScrollToPosition.End, animate: true);

Visibilidad de la barra de desplazamiento

ListView define las propiedades HorizontalScrollBarVisibility y VerticalScrollBarVisibility, que están respaldadas por propiedades enlazables. Estas propiedades obtienen o establecen un valor de enumeración ScrollBarVisibility que representa cuando la barra de desplazamiento horizontal o vertical está visible. La enumeración ScrollBarVisibility define los miembros siguientes:

  • Default indica el comportamiento predeterminado de la barra de desplazamiento para la plataforma y es el valor predeterminado de las propiedades HorizontalScrollBarVisibility y VerticalScrollBarVisibility.
  • Always indica que las barras de desplazamiento estarán visibles, incluso cuando el contenido se ajusta en la vista.
  • Never indica que las barras de desplazamiento no están visibles, incluso si el contenido no encaja en la vista.

Agregar menús contextuales

ListView admite elementos de menús contextuales, que se definen como objetos MenuItem que se agregan a la colección ViewCell.ContextActions en DataTemplate para cada elemento:

<ListView x:Name="listView"
          ItemsSource="{Binding Monkeys}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <ViewCell.ContextActions>
                    <MenuItem Text="Favorite"
                              Command="{Binding Source={x:Reference listView}, Path=BindingContext.FavoriteCommand}"
                              CommandParameter="{Binding}" />
                    <MenuItem Text="Delete"
                              Command="{Binding Source={x:Reference listView}, Path=BindingContext.DeleteCommand}"
                              CommandParameter="{Binding}" />
                </ViewCell.ContextActions>

                ...
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Los objetos MenuItem se muestran cuando se hace clic con el botón derecho en un elemento de ListView:

Screenshot of CollectionView context menu items.

Para obtener más información sobre los elementos de menú, consulta Mostrar elementos de menú.

Extraer para actualizar

ListView admite la funcionalidad deslizar para actualizar, lo que permite actualizar los datos que se muestran deslizando hacia abajo en la lista de elementos.

Para habilitar deslizar para actualizar, establece la propiedad IsPullToRefreshEnabled en true. Cuando se desencadena una actualización, ListView genera el evento Refreshing y la propiedad IsRefreshing se establecerá en true. El código necesario para actualizar el contenido del ListView debe ser ejecutado entonces por el controlador para el evento Refreshing o mediante la implementación de ICommand que ejecuta RefreshCommand. Una vez actualizado ListView, la propiedad IsRefreshing debe establecerse en false o se debe llamar al método EndRefresh en ListView, para indicar que la actualización se ha completado.

En el ejemplo siguiente se muestra un ListView que usa deslizar para actualizar:

<ListView ItemsSource="{Binding Animals}"
          IsPullToRefreshEnabled="true"
          RefreshCommand="{Binding RefreshCommand}"
          IsRefreshing="{Binding IsRefreshing}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                ...
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

En este ejemplo, cuando el usuario inicia una actualización, se ejecuta el ICommand definido por la propiedad RefreshCommand, que debe actualizar los elementos que se muestran. Mientras se produce la actualización se muestra una visualización de actualización, que consiste en un círculo de progreso animado. La propiedad IsRefreshing indica el estado actual de la operación de actualización. Cuando se desencadena una actualización, esta propiedad pasará automáticamente a true. Una vez completada la actualización, debes restablecer la propiedad a false.