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 RecycleElement
de 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 DataTemplate
devueltos 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ónIList<T>
en lugar de a una colecciónIEnumerable<T>
, ya que las coleccionesIEnumerable<T>
no admiten el acceso aleatorio. - Use las celdas integradas (como
TextCell
/SwitchCell
) en lugar deViewCell
siempre que pueda. - Use menos elementos. Por ejemplo, considere la posibilidad de usar una sola etiqueta
FormattedString
en lugar de varias. - Reemplace
ListView
porTableView
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
oGrid
para ayudar a reducir el anidamiento. - Evite un valor
LayoutOptions
específico distinto deFill
(Fill
es el más fácil de calcular). - Evite colocar un elemento
ListView
dentro deScrollView
por los siguientes motivos:ListView
implementa su propio desplazamiento.ListView
no recibirá ningún gesto, ya que el elementoScrollView
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 usadoScrollView
. 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.