ListView performance
When writing mobile applications, performance matters. Users have come to expect smooth scrolling and fast load times. Failing to meet your users' expectations will cost you ratings in the application store, or in the case of a line-of-business application, cost your organization time and money.
The Xamarin.Forms ListView
is a powerful view for displaying data, but it has some limitations. Scrolling performance can suffer when using custom cells, especially when they contain deeply nested view hierarchies or use certain layouts that require complex measurement. Fortunately, there are techniques you can use to avoid poor performance.
Caching strategy
ListViews are often used to display much more data than fits onscreen. For example, a music app might have a library of songs with thousands of entries. Creating an item for every entry would waste valuable memory and perform poorly. Creating and destroying rows constantly would require the application to instantiate and cleanup objects constantly, which would also perform poorly.
To conserve memory, the native ListView
equivalents for each platform have built-in features for reusing rows. Only the cells visible on screen are loaded in memory and the content is loaded into existing cells. This pattern prevents the application from instantiating thousands of objects, saving time and memory.
Xamarin.Forms permits ListView
cell reuse through the ListViewCachingStrategy
enumeration, which has the following values:
public enum ListViewCachingStrategy
{
RetainElement, // the default value
RecycleElement,
RecycleElementAndDataTemplate
}
Note
The Universal Windows Platform (UWP) ignores the RetainElement
caching strategy, because it always uses caching to improve performance. Therefore, by default it behaves as if the RecycleElement
caching strategy is applied.
RetainElement
The RetainElement
caching strategy specifies that the ListView
will generate a cell for each item in the list, and is the default ListView
behavior. It should be used in the following circumstances:
- Each cell has a large number of bindings (20-30+).
- The cell template changes frequently.
- Testing reveals that the
RecycleElement
caching strategy results in a reduced execution speed.
It's important to recognize the consequences of the RetainElement
caching strategy when working with custom cells. Any cell initialization code will need to run for each cell creation, which may be multiple times per second. In this circumstance, layout techniques that were fine on a page, like using multiple nested StackLayout
instances, become performance bottlenecks when they're set up and destroyed in real time as the user scrolls.
RecycleElement
The RecycleElement
caching strategy specifies that the ListView
will attempt to minimize its memory footprint and execution speed by recycling list cells. This mode doesn't always offer a performance improvement, and testing should be performed to determine any improvements. However, it's the preferred choice, and should be used in the following circumstances:
- Each cell has a small to moderate number of bindings.
- Each cell's
BindingContext
defines all of the cell data. - Each cell is largely similar, with the cell template unchanging.
During virtualization the cell will have its binding context updated, and so if an application uses this mode it must ensure that binding context updates are handled appropriately. All data about the cell must come from the binding context or consistency errors may occur. This problem can be avoided by using data binding to display cell data. Alternatively, cell data should be set in the OnBindingContextChanged
override, rather than in the custom cell's constructor, as demonstrated in the following code example:
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;
}
}
}
For more information, see Binding Context Changes.
On iOS and Android, if cells use custom renderers, they must ensure that property change notification is correctly implemented. When cells are reused their property values will change when the binding context is updated to that of an available cell, with PropertyChanged
events being raised. For more information, see Customizing a ViewCell.
RecycleElement with a DataTemplateSelector
When a ListView
uses a DataTemplateSelector
to select a DataTemplate
, the RecycleElement
caching strategy does not cache DataTemplate
s. Instead, a DataTemplate
is selected for each item of data in the list.
Note
The RecycleElement
caching strategy has a pre-requisite, introduced in Xamarin.Forms 2.4, that when a DataTemplateSelector
is asked to select a DataTemplate
that each DataTemplate
must return the same ViewCell
type. For example, given a ListView
with a DataTemplateSelector
that can return either MyDataTemplateA
(where MyDataTemplateA
returns a ViewCell
of type MyViewCellA
), or MyDataTemplateB
(where MyDataTemplateB
returns a ViewCell
of type MyViewCellB
), when MyDataTemplateA
is returned it must return MyViewCellA
or an exception will be thrown.
RecycleElementAndDataTemplate
The RecycleElementAndDataTemplate
caching strategy builds on the RecycleElement
caching strategy by additionally ensuring that when a ListView
uses a DataTemplateSelector
to select a DataTemplate
, DataTemplate
s are cached by the type of item in the list. Therefore, DataTemplate
s are selected once per item type, instead of once per item instance.
Note
The RecycleElementAndDataTemplate
caching strategy has a pre-requisite that the DataTemplate
s returned by the DataTemplateSelector
must use the DataTemplate
constructor that takes a Type
.
Set the caching strategy
The ListViewCachingStrategy
enumeration value is specified with a ListView
constructor overload, as shown in the following code example:
var listView = new ListView(ListViewCachingStrategy.RecycleElement);
In XAML, set the CachingStrategy
attribute as shown in the XAML below:
<ListView CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
...
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This method has the same effect as setting the caching strategy argument in the constructor in C#.
Set the caching strategy in a subclassed ListView
Setting the CachingStrategy
attribute from XAML on a subclassed ListView
will not produce the desired behavior, because there's no CachingStrategy
property on ListView
. In addition, if XAMLC is enabled, the following error message will be produced: No property, bindable property, or event found for 'CachingStrategy'
The solution to this issue is to specify a constructor on the subclassed ListView
that accepts a ListViewCachingStrategy
parameter and passes it into the base class:
public class CustomListView : ListView
{
public CustomListView (ListViewCachingStrategy strategy) : base (strategy)
{
}
...
}
Then the ListViewCachingStrategy
enumeration value can be specified from XAML by using the x:Arguments
syntax:
<local:CustomListView>
<x:Arguments>
<ListViewCachingStrategy>RecycleElement</ListViewCachingStrategy>
</x:Arguments>
</local:CustomListView>
ListView performance suggestions
There are many techniques for improving the performance of a ListView
. The following suggestions may improve the performance of your ListView
- Bind the
ItemsSource
property to anIList<T>
collection instead of anIEnumerable<T>
collection, becauseIEnumerable<T>
collections don't support random access. - Use the built-in cells (like
TextCell
/SwitchCell
) instead ofViewCell
whenever you can. - Use fewer elements. For example, consider using a single
FormattedString
label instead of multiple labels. - Replace the
ListView
with aTableView
when displaying non-homogenous data – that is, data of different types. - Limit the use of the
Cell.ForceUpdateSize
method. If overused, it will degrade performance. - On Android, avoid setting a
ListView
's row separator visibility or color after it has been instantiated, as it results in a large performance penalty. - Avoid changing the cell layout based on the
BindingContext
. Changing layout incurs large measurement and initialization costs. - Avoid deeply nested layout hierarchies. Use
AbsoluteLayout
orGrid
to help reduce nesting. - Avoid specific
LayoutOptions
other thanFill
(Fill
is the cheapest to compute). - Avoid placing a
ListView
inside aScrollView
for the following reasons:- The
ListView
implements its own scrolling. - The
ListView
will not receive any gestures, as they will be handled by the parentScrollView
. - The
ListView
can present a customized header and footer that scrolls with the elements of the list, potentially offering the functionality that theScrollView
was used for. For more information, see Headers and Footers.
- The
- Consider a custom renderer if you need a specific, complex design presented in your cells.
AbsoluteLayout
has the potential to perform layouts without a single measure call, making it highly performant. If AbsoluteLayout
cannot be used, consider RelativeLayout
. If using RelativeLayout
, passing Constraints directly will be considerably faster than using the expression API. This method is faster because the expression API uses JIT, and on iOS the tree has to be interpreted, which is slower. The expression API is suitable for page layouts where it only required on initial layout and rotation, but in ListView
, where it's run constantly during scrolling, it hurts performance.
Building a custom renderer for a ListView
or its cells is one approach to reducing the effect of layout calculations on scrolling performance. For more information, see Customizing a ListView and Customizing a ViewCell.