| name | android-list-performance |
| description | Optimize .NET MAUI Android list performance, especially CollectionView, CarouselView, RecyclerView-backed controls, infinite scroll, feed/product/content lists, and screens with scroll jank, slow load-more, layout shift, nested lists, or Android-specific frame drops. Use when the user asks to improve Android lists, melhorar listas Android, otimizar CollectionView, fix jank/travamento/engasgo/scroll lento, apply Rommanel list performance practices, or adapt the performance-android-listas.md guidance to a MAUI page. |
Android List Performance
Use this skill to improve MAUI list screens for Android without broad refactors. Prefer small, measurable changes that preserve the existing UI and data flow.
For the full source guide, read references/performance-android-listas.md when you need exact examples, metrics, or deeper rationale.
Workflow
-
Find the target screen and its ViewModel.
- Search for
CollectionView, CarouselView, ListView, third-party list controls, RemainingItemsThreshold, Scrolled, ItemsSource, and synchronous waits such as .Result or .GetAwaiter().GetResult().
- Identify whether the list is native MAUI
CollectionView or a third-party collection component.
-
Classify the bottleneck.
- Severe jank while scrolling: check third-party lists, nested RecyclerViews,
MeasureAllItems, heavy shadows/templates, and image layout shifts.
- Jank on load-more: check collection replacement, large page sizes, synchronous async calls, and expensive per-item work.
- Layout shift: check
Auto rows around images and missing HeightRequest/stable dimensions.
- First scroll delay: check initial page size, heavy headers, carousels in headers/items, and inline complex templates.
-
Apply the narrowest useful fixes.
- Use native MAUI
CollectionView where practical.
- Set
ItemSizingStrategy="MeasureFirstItem" when item heights are stable or can be made stable.
- Define fixed or predictable item/image dimensions using
HeightRequest, fixed RowDefinitions, aspect-ratio equivalents in XAML, or stable grid rows.
- Add
CompressedLayout.IsHeadless="True" to root template containers when safe.
- Use
Mode=OneTime for item data that does not change during the item lifetime: titles, authors, dates, image URLs, counts loaded from the server, labels, IDs.
- Avoid replacing the
ItemsSource during load-more. Add items to the existing observable collection instead.
- Reduce page sizes when templates are complex. Favor smaller initial pages over rendering many views at once.
- Avoid nested RecyclerViews. Replace small horizontal lists with
BindableLayout or HorizontalStackLayout inside ScrollView when item count is low. If a carousel must remain, limit items and avoid partial peeking.
- Remove blocking async calls on the UI path. Convert
.Result and .GetAwaiter().GetResult() to async flows, often with Task.WhenAll for per-item enrichment.
-
Add Android RecyclerView tuning only in code-behind when the screen owns a native CollectionView.
#if ANDROID
using AndroidX.RecyclerView.Widget;
#endif
private static void ConfigureAndroidRecyclerView(CollectionView collectionView)
{
#if ANDROID
void Apply()
{
if (collectionView.Handler?.PlatformView is not RecyclerView recyclerView)
{
return;
}
recyclerView.GetRecycledViewPool().SetMaxRecycledViews(0, 20);
recyclerView.SetItemViewCacheSize(3);
}
collectionView.HandlerChanged += (_, _) => Apply();
Apply();
#endif
}
Call this after InitializeComponent() for the target CollectionView. Keep the method local to the page unless multiple screens already share a local helper pattern.
- Validate.
- Run the narrowest relevant build, usually
dotnet build <app csproj> -f <android tfm> -c Debug --no-restore.
- Run
git diff --check.
- If a device is available and the user asked for runtime validation, test scroll with
adb logcat or native interaction.
XAML Patterns
Prefer this shape for variable-looking cards that can be made stable:
<CollectionView
ItemsSource="{Binding Items}"
ItemsLayout="{StaticResource ListLayout}"
ItemSizingStrategy="MeasureFirstItem"
RemainingItemsThreshold="6"
RemainingItemsThresholdReached="OnLoadMore">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Item">
<Grid
CompressedLayout.IsHeadless="True"
HeightRequest="330"
RowDefinitions="*">
<Border>
<Grid HeightRequest="330" RowDefinitions="170,100,60">
<Border HeightRequest="170">
<ffimageloading:CachedImage
HeightRequest="170"
Aspect="AspectFill"
DownsampleToViewSize="True"
Source="{Binding ImageUrl, Mode=OneTime}" />
</Border>
<Label
Grid.Row="1"
Text="{Binding Title, Mode=OneTime}" />
</Grid>
</Border>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Avoid ItemSizingStrategy="MeasureAllItems" on Android list screens unless item heights truly cannot be stabilized.
ViewModel Patterns
Do not recreate the visible collection during load-more:
foreach (var item in nextItems)
{
Items.Add(item);
}
For refresh, clearing the existing observable collection is usually cheaper than assigning a new collection because the CollectionView keeps its binding:
Items.Clear();
foreach (var item in firstPage)
{
Items.Add(item);
}
If adding many items at once and the project already uses an observable range collection, prefer its range API. Do not introduce a new collection library only for this unless the list is still slow after simpler fixes.
Guardrails
- Preserve user-facing layout unless the optimization requires stable dimensions.
- Do not remove features such as pull-to-refresh, selection, or tap commands unless replacing them with equivalent behavior.
- Do not add instrumentation permanently unless the user asks for profiling. Temporary
PerfLog/logcat code is useful for diagnosis but should be removed or kept behind debug-only conditions.
- Treat unrelated dirty files as user work. Do not revert them.