name: nalu-maui-virtualscroll description: Nalu.Maui.VirtualScroll: high-performance virtualized list (RecyclerView/UICollectionView). Use when building lists, carousels, sectioned data, pull-to-refresh, drag-and-drop reorder, or replacing MAUI CollectionView for performance.
Nalu.Maui.VirtualScroll
High-performance virtualized scrolling (Android RecyclerView, iOS UICollectionView). Replaces MAUI CollectionView with dynamic item sizing, headers/footers, sections, carousel, pull-to-refresh, and drag-and-drop.
When to use
- Long or complex lists where CollectionView performance is insufficient.
- Sectioned data, carousel, or reorderable lists.
- AOT builds: use
IVirtualScrollAdapter(factory methods), not rawObservableCollectionbinding.
Setup
builder.UseNaluVirtualScroll();
ItemsSource and adapters
- IVirtualScrollAdapter (recommended, AOT-compatible):
VirtualScroll.CreateObservableCollectionAdapter(items)orCreateObservableCollectionAdapter(sections, s => s.Items); for static data:CreateStaticCollectionAdapter(items)or with sections. - ObservableCollection<T> (not AOT): Auto-wrapped; full change notifications.
- IEnumerable: Static list; no change notifications.
Wrap item content in nalu:ViewBox for best performance.
<nalu:VirtualScroll ItemsSource="{Binding Adapter}">
<nalu:VirtualScroll.ItemTemplate>
<DataTemplate x:DataType="models:MyItem">
<nalu:ViewBox>
<Label Text="{Binding Name}" Padding="16" />
</nalu:ViewBox>
</DataTemplate>
</nalu:VirtualScroll.ItemTemplate>
</nalu:VirtualScroll>
Templates
- ItemTemplate (required for items), HeaderTemplate, FooterTemplate (binding context = page/VM).
- SectionHeaderTemplate, SectionFooterTemplate for sectioned adapters.
- DataTemplateSelector: All template properties accept a
DataTemplateSelectorfor heterogeneous item types (e.g. different templates by item type). Set the selector as the template value; use sparingly—many templates can reduce recycling efficiency.
<nalu:VirtualScroll.ItemTemplate>
<local:MyItemTemplateSelector
TextTemplate="{StaticResource TextItemTemplate}"
ImageTemplate="{StaticResource ImageItemTemplate}" />
</nalu:VirtualScroll.ItemTemplate>
Layouts
{nalu:VerticalVirtualScrollLayout}(default),{nalu:HorizontalVirtualScrollLayout}.- Carousel:
HorizontalCarouselVirtualScrollLayout,VerticalCarouselVirtualScrollLayout; bindCarouselVirtualScrollLayout.CurrentRangetointorVirtualScrollRangefor current index. - Optional:
EstimatedItemSize,EstimatedHeaderSize,EstimatedSectionHeaderSize, etc. on layout for iOS.
Scrolling
- ScrollTo(sectionIndex, itemIndex) or ScrollTo(item); overloads with
ScrollToPosition(MakeVisible, Start, Center, End) andanimated. - ScrolledCommand / OnScrolled:
VirtualScrollScrolledEventArgs(ScrollX/Y, ScrollPercentageY, RemainingScrollY, etc.). Use for progress/infinite scroll; do not callGetVisibleItemsRange()inside scroll handlers (expensive). - ScrollStartedCommand / ScrollEndedCommand for gesture start/end.
See Scrolling for visible range and infinite-scroll patterns.
Pull-to-refresh
IsRefreshEnabled="True", RefreshCommand, IsRefreshing, RefreshAccentColor. RefreshCommand receives a completion callback—invoke it when done: completionCallback(); in a RelayCommand or args.Complete() with OnRefresh event.
Drag and drop
Bind DragHandler to the same adapter as ItemsSource (adapters implement IReorderableVirtualScrollAdapter). Only data items are reorderable; headers/footers are not. See Drag & Drop for custom behavior.
<nalu:VirtualScroll ItemsSource="{Binding Adapter}" DragHandler="{Binding Adapter}">
Performance tips
- Use
nalu:ViewBoxin item templates. - Keep item templates simple.
- For AOT: use adapter factory methods; do not bind
ObservableCollectiondirectly. - Prefer
ObservableRangeCollection<T>for bulk updates. - Do not call
GetVisibleItemsRange()in scroll handlers; useScrollPercentageYorRemainingScrollYfor infinite scroll.
Caveats
- License: Non-commercial use under an Apache 2.0-Based Non-Commercial License; commercial usage rights require GitHub Sponsors. See package LICENSE.
- Platform: Android and iOS/Mac Catalyst only; no Windows.
- AOT: Must use
IVirtualScrollAdapter(e.g. from factory); bindingObservableCollectiondirectly throws in AOT.