maui-collectionview
CollectionView – .NET MAUI
Use CollectionView for displaying scrollable lists and grids of data.
It replaces ListView and offers better performance, flexible layouts, and no ViewCell requirement.
Basic setup
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Item">
<HorizontalStackLayout Padding="8" Spacing="8">
<Image Source="{Binding Icon}" WidthRequest="40" HeightRequest="40" />
<Label Text="{Binding Name}" VerticalOptions="Center" />
</HorizontalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
- Bind
ItemsSourceto anObservableCollection<T>so the UI updates on add/remove. - Each item template root must be a
LayoutorView— never useViewCell. - Always set
x:DataTypeonDataTemplatefor compiled bindings.
DataTemplateSelector
Choose templates at runtime based on item properties:
public class ItemTemplateSelector : DataTemplateSelector
{
public DataTemplate NormalTemplate { get; set; }
public DataTemplate HighlightTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
return ((Item)item).IsHighlighted ? HighlightTemplate : NormalTemplate;
}
}
<CollectionView ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource MyTemplateSelector}" />
Layouts
Set ItemsLayout to control arrangement. Default is VerticalList.
| Layout | XAML value |
|---|---|
| Vertical list | VerticalList (default) |
| Horizontal list | HorizontalList |
| Vertical grid | GridItemsLayout with Orientation="Vertical" |
| Horizontal grid | GridItemsLayout with Orientation="Horizontal" |
Grid layout
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2"
VerticalItemSpacing="8"
HorizontalItemSpacing="8" />
</CollectionView.ItemsLayout>
</CollectionView>
Horizontal list
<CollectionView ItemsSource="{Binding Items}"
ItemsLayout="HorizontalList" />
ItemSizingStrategy
Controls how items are measured. Set on ItemsLayout.
| Value | Behavior |
|---|---|
MeasureAllItems |
Measures every item individually (default). Accurate but slower for heterogeneous sizes. |
MeasureFirstItem |
Measures only the first item and applies that size to all. Much faster for uniform items. |
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
ItemSizingStrategy="MeasureFirstItem" />
</CollectionView.ItemsLayout>
ItemSpacing
Use ItemSpacing on LinearItemsLayout or VerticalItemSpacing / HorizontalItemSpacing on GridItemsLayout:
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" ItemSpacing="8" />
</CollectionView.ItemsLayout>
Headers and footers
Supports string, view, or templated:
<!-- Simple string -->
<CollectionView Header="My Items" Footer="End of list" />
<!-- Custom view -->
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.Header>
<Label Text="Header" FontAttributes="Bold" Padding="8" />
</CollectionView.Header>
<CollectionView.Footer>
<Label Text="Footer" FontAttributes="Italic" Padding="8" />
</CollectionView.Footer>
</CollectionView>
Use HeaderTemplate / FooterTemplate when headers/footers are data-bound.
Selection
Selection mode
| Mode | Property to bind | Binding mode |
|---|---|---|
None |
— | — |
Single |
SelectedItem |
TwoWay |
Multiple |
SelectedItems |
OneWay |
<CollectionView ItemsSource="{Binding Items}"
SelectionMode="Single"
SelectedItem="{Binding CurrentItem, Mode=TwoWay}"
SelectionChangedCommand="{Binding ItemSelectedCommand}" />
For Multiple selection, bind SelectedItems (type IList<object>):
<CollectionView SelectionMode="Multiple"
SelectedItems="{Binding ChosenItems, Mode=OneWay}" />
Selected visual state
Highlight selected items using VisualStateManager:
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Item">
<Grid Padding="8">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Normal">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Transparent" />
</VisualState.Setters>
</VisualState>
<VisualState Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Label Text="{Binding Name}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
Grouping
- Create a group class inheriting from
List<T>:
public class AnimalGroup : List<Animal>
{
public string Name { get; }
public AnimalGroup(string name, List<Animal> animals) : base(animals)
{
Name = name;
}
}
- Bind to
ObservableCollection<AnimalGroup>and setIsGrouped="True":
<CollectionView ItemsSource="{Binding AnimalGroups}"
IsGrouped="True">
<CollectionView.GroupHeaderTemplate>
<DataTemplate x:DataType="models:AnimalGroup">
<Label Text="{Binding Name}"
FontAttributes="Bold"
BackgroundColor="{StaticResource Gray100}"
Padding="8" />
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
<CollectionView.GroupFooterTemplate>
<DataTemplate x:DataType="models:AnimalGroup">
<Label Text="{Binding Count, StringFormat='{0} items'}"
FontAttributes="Italic"
Padding="4,0" />
</DataTemplate>
</CollectionView.GroupFooterTemplate>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Animal">
<Label Text="{Binding Name}" Padding="16,4" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
EmptyView
Shown when ItemsSource is empty or null.
<!-- Simple string -->
<CollectionView EmptyView="No items found." />
<!-- Custom view — wrap in a ContentView -->
<CollectionView ItemsSource="{Binding SearchResults}">
<CollectionView.EmptyView>
<ContentView>
<VerticalStackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Image Source="empty_state.png" WidthRequest="120" />
<Label Text="Nothing here yet" HorizontalTextAlignment="Center" />
</VerticalStackLayout>
</ContentView>
</CollectionView.EmptyView>
</CollectionView>
You can also use EmptyViewTemplate with a DataTemplateSelector to swap empty views based on state.
Gotcha: When using a custom view for
EmptyView, wrap it in aContentViewto avoid layout issues.
Scrolling
ScrollTo
Programmatically scroll by index or item:
// Scroll to index
collectionView.ScrollTo(index: 10, position: ScrollToPosition.Center, animate: true);
// Scroll to item
collectionView.ScrollTo(item: myItem, position: ScrollToPosition.MakeVisible, animate: true);
| ScrollToPosition | Behavior |
|---|---|
MakeVisible |
Scrolls just enough to make the item visible |
Start |
Scrolls item to the start of the viewport |
Center |
Scrolls item to the center of the viewport |
End |
Scrolls item to the end of the viewport |
Snap points
Control snap behavior after scrolling:
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal"
SnapPointsType="MandatorySingle"
SnapPointsAlignment="Center" />
</CollectionView.ItemsLayout>
SnapPointsType:None,Mandatory,MandatorySingleSnapPointsAlignment:Start,Center,End
Incremental loading (infinite scroll)
Load more data as the user scrolls near the end:
<CollectionView ItemsSource="{Binding Items}"
RemainingItemsThreshold="5"
RemainingItemsThresholdReachedCommand="{Binding LoadMoreCommand}" />
Or handle the RemainingItemsThresholdReached event in code-behind.
Gotcha: Do not use incremental loading with a
StackLayout-basedItemsLayout. AStackLayouthas no concept of virtualization and will trigger infinite threshold-reached events. Use the defaultLinearItemsLayoutorGridItemsLayout.
SwipeView integration
Wrap item content in SwipeView for swipe actions:
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Item">
<SwipeView>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem Text="Delete"
BackgroundColor="Red"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:MainViewModel}}, Path=DeleteCommand}"
CommandParameter="{Binding}" />
</SwipeItems>
</SwipeView.RightItems>
<Grid Padding="12">
<Label Text="{Binding Name}" />
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
Pull-to-refresh with RefreshView
Wrap CollectionView in a RefreshView:
<RefreshView IsRefreshing="{Binding IsRefreshing}"
Command="{Binding RefreshCommand}">
<CollectionView ItemsSource="{Binding Items}">
<!-- ItemTemplate -->
</CollectionView>
</RefreshView>
Set IsRefreshing back to false in the view model when the refresh completes.
Common gotchas
| Issue | Fix |
|---|---|
| UI doesn't update when items change | Use ObservableCollection<T>, not List<T>. |
| App crashes or blank items | Never use ViewCell — use Grid, StackLayout, or any View as template root. |
| Items disappear or layout breaks | Always update ItemsSource and the collection on the UI thread (MainThread.BeginInvokeOnMainThread). |
| Incremental loading fires endlessly | Don't use StackLayout as layout; use LinearItemsLayout or GridItemsLayout. |
| EmptyView doesn't render correctly | Wrap custom empty views in ContentView. |
| Poor scroll performance | Use MeasureFirstItem sizing strategy for uniform item sizes. |
| Selected state not visible | Add VisualState Name="Selected" to the item template root element. |
| Binding errors in SwipeView commands | Use RelativeSource AncestorType to reach the view model from inside the item template. |