Improving CollectionView Scrolling
Sometimes scrolling a Xamarin.Forms.CollectionView (especially on Android) can be choppy. Let's take a look at how to fix it!
Background
I was first alerted by @anaseeen on Twitter that my GitTrends app had substantial jitter when scrolling the CollectionView
.
I verified the jitter was also happening on my Google Pixel 3. Because the Pixel 3 has substantial CPU and RAM resources, this test confirmed that this problem is not device-specific and there is a performance issue in my code.
Solution
I found that two things were causing the UI to jitter when the user scrolls a CollectionView:
- Bindings
- Garbage Collection
I was able to remove the jitter by removing bindings from my DataTemplate
and increasing the size of the Android Nursery which decreases the frequency of garbage collections.
If you'd like to see exactly how I solved it, here is the Pull Request that fixed it in my GitTrends app: https://github.com/brminnick/GitTrends/pull/143
Before (Substantial Scrolling Jitter) | After (No Scrolling Jitter) |
---|---|
Removing DataTemplate Bindings
To remove bindings, we can use a DataTemplateSelector
to pass the BindingContext
directly into our DataTemplate
and assign the values on initialization.
Before Removing DataTemplate Bindings
using Xamarin.Forms.Markup;
class CollectionViewPage : ContentPage
{
Content = new CollectionView
{
ItemTemplate = new ImageDataTemplate()
}.Bind(CollectionView.ItemsSourceProperty, nameof(CollectionViewModel.ImageList));
}
class ImageModel
{
public string ImageTitle { get; set; }
public string ImageUrl { get; set; }
}
class ImageDataTemplate : DataTemplate
{
public MyDataTemplate() : base(() => CreateDataTemplate())
{
}
static View CreateDataTemplate() => new StackLayout
{
Children =
{
new Image().Bind(Image.SourceProperty, nameof(ImageModel.ImageUrl))
new Label().Bind(Label.TextProperty, nameof(ImageModel.ImageTitle))
}
}
}
After Removing DataTemplate Bindings
using Xamarin.Forms.Markup;
class CollectionViewPage : ContentPage
{
Content = new CollectionView
{
ItemTemplate = new ImageDataTemplateSelector()
}.Bind(CollectionView.ItemsSourceProperty, nameof(CollectionViewModel.ImageList));
}
class ImageModel
{
public string ImageTitle { get; set; }
public string ImageUrl { get; set; }
}
class ImageDataTemplateSelector : DataTemplateSelector
{
protected override DataTemplate OnSelectTemplate(object item, BindableObject container) => new ImageDataTemplate((ImageModel)item);
class ImageDataTemplate : DataTemplate
{
public MyDataTemplate(ImageModel imageModel) : base(() => CreateDataTemplate(imageModel))
{
}
static View CreateDataTemplate(ImageModel imageModel) => new StackLayout
{
Children =
{
new Image { Source = imageModel.ImageUrl },
new Label { Text = imageModel.ImageTitle }
}
}
}
}
Increase Nursery Size to Decrease Garbage Collection
We can set the size of the Android Nursery by setting it in the MONO_GC_PARAMS
:
1. In the Xamarin.Android project, create a new file called GarbageCollector.config
2. In GarbageCollector.config
, add the following line of code:
MONO_GC_PARAMS=nursery-size=64m
Note: The value fornursery-size
must be a power of 2 (e.g. 2, 4, 8, 16, 32, 64, 128 etc)
Note: I recommend trying different values for yournursery-size
because each app is different and will have different memory requirements
3. In Visual Studio, in the Solution Explorer, right-click on GarbageCollector.config
4. In the right-click menu, select BuildAction > AndroidEnvironment