Compartir vía


Rendimiento de ListView

Al escribir aplicaciones para dispositivos móviles, el rendimiento es importante. Los usuarios esperan un desplazamiento sin problemas y tiempos de carga rápidos. Si no se cumplen las expectativas de los usuarios, le costará clasificaciones en la tienda de aplicaciones o, en el caso de una aplicación de línea de negocio, tiempo y dinero para la organización.

Xamarin.FormsListView 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 se pueden usar para evitar un mal rendimiento de :

Estrategia de almacenamiento en caché

Los objetos ListView se suelen usar para mostrar mucho más datos de los que caben en 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 funcionaría mal. La creación y destrucción constante de filas requeriría que la aplicación cree instancias y limpie objetos constantemente, lo que también afectaría negativamente al funcionamiento.

Para conservar la memoria, los equivalentes nativos ListView de cada plataforma tienen características integradas para reutilizar filas. Solo las celdas visibles en la pantalla se cargan en 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.

Xamarin.Forms permite la reutilización de celdas de ListView mediante la enumeración ListViewCachingStrategy, que tiene los siguientes valores:

public enum ListViewCachingStrategy
{
    RetainElement,   // the default value
    RecycleElement,
    RecycleElementAndDataTemplate
}

Nota:

La Plataforma universal de Windows (UWP) omite la estrategia de almacenamiento en caché de RetainElement, ya que siempre usa el almacenamiento en caché para mejorar el rendimiento. Por tanto, se comporta de manera predeterminada como si se aplicara la estrategia de almacenamiento en caché de RecycleElement.

RetainElement

La estrategia RetainElement de almacenamiento en caché especifica que ListView generará una celda para cada elemento de la lista y es el comportamiento ListView predeterminado. 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 RecycleElement de almacenamiento en caché da como resultado una velocidad de ejecución reducida.

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

RecycleElement

La estrategia RecycleElementde almacenamiento en caché 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 las mejoras. Pero es la opción preferida y debe usarse en las siguientes circunstancias:

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

Durante la virtualización, se actualiza el contexto de enlace de la celda, por lo que si una aplicación usa este modo, debe asegurarse de que las actualizaciones del contexto de enlace se controlan correctamente. Todos los datos sobre la celda deben provenir del contexto de enlace o de los errores de coherencia. Este problema se puede evitar mediante el enlace de datos para mostrar datos de celda. Como alternativa, los datos de celda se deben establecer en la invalidación de OnBindingContextChanged, en lugar de en el constructor de la celda personalizada, como se muestra en el ejemplo de código 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;
        }
    }
}

Para más información, vea Cambios de contexto de enlace.

En iOS y Android, si las celdas usan representadores personalizados, deben asegurarse de que la notificación de cambio de propiedad se implemente correctamente. 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.

RecycleElement con DataTemplateSelector

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

Nota:

La estrategia de almacenamiento en caché de RecycleElement tiene un requisito previo, introducido en Xamarin.Forms 2.4: cuando se pide a una instancia de DataTemplateSelector seleccionar un elemento DataTemplate, cada instancia de 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.

RecycleElementAndDataTemplate

La estrategia de almacenamiento en caché de RecycleElementAndDataTemplate se basa en la estrategia de almacenamiento en caché de RecycleElement al asegurarse de que, cuando ListView usa DataTemplateSelector para seleccionar DataTemplate, las instancias de DataTemplate se almacenan en caché por el tipo de elemento de la lista. Por tanto, se seleccionan instancias de DataTemplate una vez por cada tipo de elemento, en lugar de una vez por cada instancia de elemento.

Nota:

La estrategia de almacenamiento en caché de RecycleElementAndDataTemplate tiene un requisito previo: los elemento DataTemplatedevueltos por DataTemplateSelector deben usar el constructor de DataTemplate que toma una instancia de Type.

Establecer la estrategia de almacenamiento en caché

El valor de enumeración ListViewCachingStrategy se especifica con una sobrecarga de constructor ListView, como se muestra en el ejemplo de código siguiente:

var listView = new ListView(ListViewCachingStrategy.RecycleElement);

En XAML, establezca el atributo CachingStrategy como se muestra en el código XAML siguiente:

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

Este método tiene el mismo efecto que establecer el argumento de estrategia de almacenamiento en caché en el constructor en C#.

Establecer la estrategia de almacenamiento en caché en una listView con subclase

Establecer el atributo CachingStrategy de XAML en una subclase ListView no generará el comportamiento deseado, ya que no hay ninguna propiedad CachingStrategy en ListView. Además, si XAMLC está habilitado, se generará el siguiente mensaje de error: No se ha encontrado ninguna propiedad, propiedad enlazable o evento para "CachingStrategy"

La solución a este problema consiste en especificar un constructor en la subclase de ListView que acepte un parámetro ListViewCachingStrategy y lo pase 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 la sintaxis x:Arguments:

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

Sugerencias de rendimiento de ListView

Hay muchas técnicas para mejorar el rendimiento de una instancia de ListView. Las sugerencias siguientes pueden mejorar el rendimiento de la instancia de ListView

  • Enlace la propiedad ItemsSource a una colección IList<T> en lugar de a una colección IEnumerable<T>, ya que las colecciones IEnumerable<T> no admiten el acceso aleatorio.
  • Use las celdas integradas (como TextCell / SwitchCell ) en lugar de ViewCell siempre que pueda.
  • Use menos elementos. Por ejemplo, considere la posibilidad de usar una sola etiqueta FormattedString en lugar de varias.
  • Reemplace ListView por TableView al mostrar datos no homogéneos, es decir, datos de diferentes tipos.
  • Limite el uso del método Cell.ForceUpdateSize. Si se usa en exceso, se degradará el rendimiento.
  • En Android, evite establecer la visibilidad o el color del separador de fila de ListView después de crear una instancia, ya que como resultado se degrada el rendimiento.
  • Evite cambiar el diseño de celda en función de BindingContext. El cambio de diseño incurre en grandes costos de medición e inicialización.
  • Evite jerarquías de diseño profundamente anidadas. Use AbsoluteLayout o Grid para ayudar a reducir el anidamiento.
  • Evite un valor LayoutOptions específico distinto de Fill (Fill es el más fácil de calcular).
  • Evite colocar un elemento ListView dentro de ScrollView por los siguientes motivos:
    • ListView implementa su propio desplazamiento.
    • ListView no recibirá ningún gesto, ya que el elemento ScrollView primario los controlará.
    • ListView puede presentar un encabezado y pie de página personalizados que se desplazan con los elementos de la lista, lo que podría ofrecer la funcionalidad para la que se ha usado ScrollView. Para más información, vea Encabezados y pies de página.
  • Considere la posibilidad de usar un representador personalizado si necesita un diseño específico y complejo en las celdas.

AbsoluteLayout tiene la posibilidad de realizar diseños sin una sola llamada de medida, lo que hace que sea muy eficaz. Si no se puede usar AbsoluteLayout, considere la posibilidad de RelativeLayout. Si usa RelativeLayout, pasar restricciones directamente será considerablemente más rápido que usar la API de expresión. Este método es más rápido porque la API de expresión usa JIT y, en iOS, el árbol se debe interpretar, lo que es más lento. La API de expresión es adecuada para diseños de página donde solo es necesario en el diseño inicial y la rotación, pero en ListView, donde se ejecuta constantemente durante el desplazamiento, afecta al rendimiento.

La creación de un representador personalizado para ListView o sus celdas es un enfoque para reducir el efecto de los cálculos de diseño en el rendimiento del desplazamiento. Para más información, vea Personalización de ListView y Personalización de ViewCell.