Xamarin.Forms.CollectionView Spec #3172
Description
CollectionView
The current Forms ListView design and implementations are very complex and challenging to maintain, while often not providing the flexibility that users would like. Because of the complexity of the implementations and the backward compatibility requirements, ListView bugs can be difficult to find and fix.
The goal of the CollectionView effort is to address these problems by simplifying the API and embracing the capabilities of the native platforms' list-building controls. To that end:
-
CollectionView removes the concept of Cells entirely. While convenient in some cases, the Cell concept introduces a great deal of complexity into the native implementations of ListView. Everything that is possible with Cells can be accomplished with reusable DataTemplates.
-
CollectionView reduces the API surface. Several properties and events from ListView are not available in CollectionView. Several of these are easily replaceable within DataTemplates; others were very specific to particular platforms and never really belonged in the first place. A list of these changes can be found below.
-
CollectionView aims to be more flexible by not baking in assumptions about layout. This allows us to support layouts which users have long been asking for (e.g., a HorizontalListView) and which the native implementations already provide.
Note: Nothing in this document should be taken as an indication that the current ListView will be removed or will cease to be maintained. This effort is simply aiming to provide an alternative control which will more easily serve the needs of many Forms users.
Note: Nothing in this specification is guaranteed to be final; all features, implementations, and interfaces are subject to change.
Current completion status of the features specified below
Supporting APIs
In order to provide users with a more modern list, the CollectionView effort will include a some supporting APIs:
-
FontIconSource - using scalable glyphs as icons; the intent is to support glyphs whereever Forms currently supports images. This is obviously a desirable feature in many parts of Forms, but it's also absolutely essential to support contextual swipe gestures in CollectionView (see the FontIconSource spec).
-
SwipeView - Provides support for swiping on an element to execute or reveal further actions (see the SwipeView spec).
Layout Specification
This spec provides for the developer to specify whether items to be laid out in a vertical list, horizontal list, or a grid. The underlying native classes all support these layout types. (Note that on iOS, this spec assumes the use of UICollectionView rather than UITableView.)
The specifications given to the CollectionView are mapped to native layouts in each renderer. For example, if a grid layout is specified, on iOS the renderer will (by default) use a UICollectionViewFlowLayout. On Android, the default will be GridLayoutManager; on UWP, GridView.
Forms does not participate in the layout of the items in the repeater (beyond generating the items Views themselves); this portion of the layout is entirely native.
The selection of layout methods to use in each renderer will be user-modifiable; if a user would rather use StaggeredGridLayoutManager on Android, this could be achieved by modifying the selection method and returning the desired layout manager.
ListView API Removals
- IsPullToRefreshEnabled - this is now handled by the RefreshView.
- IsRefreshing - this is now handled by the RefreshView.
- RefreshCommand - this is now handled by the RefreshView.
- HasUnevenRows - this is now ItemSizingStrategy.
- RowHeight - this is now determined by the first item to be laid out.
- SeparatorVisibility - CollectionView does not include built-in separators; users can provide these (if desired) in their templates.
- SeparatorColor - CollectionView does not include built-in separators; users can provide these (if desired) in their templates.
- GroupShortName - this property existed to support jump lists and semantic zoom; CollectionView phase 1 does not support these features.
API
ContextItem
ContextItem provides a way to specify a contextual interaction, often displayed as part of a context menu.
public class ContextItem
{
public static readonly BindableProperty TextProperty;
public string Text { get; set; }
public static readonly BindableProperty CommandProperty;
public ICommand Command { get; set; }
public static readonly BindableProperty CommandParameterProperty;
public object CommandParameter { get; set; }
public static readonly BindableProperty IsEnabledProperty;
public bool IsEnabled { get; set; }
public static readonly BindableProperty IconProperty;
public ImageSource Icon { get; set; }
public event EventHandler Invoked;
}
Properties
API | Description |
---|---|
Text | Gets or sets the text description displayed on the item. |
Command | Gets or sets the command to execute when this item is invoked. |
CommandParameter | |
IsEnabled | Gets or sets a value indicating whether the user can interact with the context item. |
Icon | Gets or sets the graphic content of the item. |
Events
API | Description |
---|---|
Invoked | Occurs when user interaction indicates that the command represented by this item should execute. |
ContextMenu
Provides a way to specify a set of interactions to be displayed in a context menu. A ContextMenu can be provided for any VisualElement, and is activated/displayed according to the native platform conventions.
public static class ContextMenu
{
public static readonly BindableProperty MenuItemsProperty =
BindableProperty.CreateAttached("MenuItems", typeof(ContextMenuItems), typeof(VisualElement));
}
public class ContextMenuItems : IList<ContextItem>
{
}
RefreshView
Moved to #5882
SelectionMode
Provides the selection mode behaviors for a CollectionView.
public enum SelectionMode
{
None,
Single,
Multiple
}
SelectionChangedEventArgs
public class SelectionChangedEventArgs : EventArgs
{
public IReadOnlyList<object> PreviousSelection { get; }
public IReadOnlyList<object> CurrentSelection { get; }
}
Properties
API | Description |
---|---|
PreviousSelection | Gets the list of items that were selected before the selection changed. |
CurrentSelection | Gets the list of items that are selected after the selection change. |
IItemsLayout
Marker interface to indicate that the implementing class specifies a layout used by CollectionView to arrange its items.
public interface IItemsLayout {}
ItemsLayoutOrientation
Enumerates the possible orientations for an ItemsLayout
. As items are added, the CollectionView expands in the orientation direction.
public enum ItemsLayoutOrientation
{
Vertical,
Horizontal
}
ItemsLayout
Base class for Forms-provided items layouts.
public abstract class ItemsLayout : IItemsLayout
{
public ItemsLayoutOrientation Orientation { get; }
protected ItemsLayout(ItemsLayoutOrientation orientation);
public static readonly BindableProperty SnapPointsAlignmentProperty;
public SnapPointsAlignment SnapPointsAlignment { get; set; }
public static readonly BindableProperty SnapPointsTypeProperty;
public SnapPointsType SnapPointsType { get; set; }
public static readonly BindableProperty ItemSpacingProperty;
public Thickness ItemSpacing {get; set;}
}
Properties
API | Description |
---|---|
ItemsLayoutOrientation | Specifies the direction in the which the CollectionView expands as items are added. |
SnapPointsType | Specifies the behavior of snap points when scrolling the view. |
SnapPointsAlignment | Specifies how snap points are aligned with items in the view. |
ItemSpacing | Specifies the empty space around each item. |
ListItemsLayout
public class ListItemsLayout : ItemsLayout
{
public ListItemsLayout(ItemsLayoutOrientation orientation) : base (orientation);
public static readonly IItemsLayout VerticalList = new ListItemsLayout(ItemsLayoutOrientation.Vertical);
public static readonly IItemsLayout HorizontalList = new ListItemsLayout(ItemsLayoutOrientation.Horizontal);
}
Static Members
API | Description |
---|---|
VerticalList | Specifies a single column list in which the list grows vertically as new items are added. |
HorizontalList | Specifies a single row list in which the list grows horizontally as new items are added. |
GridItemsLayout
Defines a multi-row or multi-column layout.
public class GridItemsLayout : ItemsLayout
{
public static readonly BindableProperty SpanProperty;
public int Span { get; set; }
public GridItemsLayout([Parameter("Span")] int span, [Parameter("Orientation")] ItemsLayoutOrientation orientation) : base (orientation);
}
SnapPointsAlignment
Enumerates the possible alignments for snap points in a ListItemsLayout
.
public enum SnapPointsAlignment
{
Start,
Center,
End
}
Enum Values
API | Description |
---|---|
Start | Snap points are aligned with the leading edge of items. |
Center | Snap points are aligned with the center of items. |
End | Snap points are aligned with the trailing edge of items. |
SnapPointsType
Enumerates the possible behaviors for snap points in a ListItemsLayout
.
public enum SnapPointsType
{
None,
Optional,
Mandatory,
OptionalSingle,
MandatorySingle,
}
Enum Values
API | Description |
---|---|
None | Scrolling does not snap to items. |
Optional | Content snaps to the closest snap point to where scrolling would naturally stop along the direction of inertia, if any snap points are sufficiently close. |
Mandatory | Content always snaps to the closest snap point to where scrolling would naturally stop along the direction of inertia. |
OptionalSingle | Same behavior as Optional, but only scrolls one item at a time. |
MandatorySingle | Same behavor as Mandatory, but only scrolls one item at a time. |
Properties
API | Description |
---|---|
Span | Specifies the number of items to lay out in the constrained direction. |
ItemSizingStrategy
Provides the possible item measurement strategies to be used by the CollectionView.
public enum ItemSizingStrategy
{
MeasureAllItems,
MeasureFirstItem
}
Enum Values
API | Description |
---|---|
MeasureAllItems | Each item is measured individually. |
MeasureFirstItem | Only the first item is measured; all subsequent items are assumed to be the same size as the first. |
ItemsUpdatingScrollMode
Defines constants that specify the scrolling behavior of items while updating.
public enum ItemsUpdatingScrollMode
{
KeepItemsInView,
KeepScrollOffset,
KeepLastItemInView
}
Enum Values
API | Description |
---|---|
KeepItemsInView | Adjusts the scroll offset to keep the first visible item in the viewport when items are added. |
KeepScrollOffset | Maintains the scroll offset relative to the beginning of the list when items are added. |
KeepLastItemInView | Adjusts the scroll offset to keep the last visible item in the viewport when items are added. |
CollectionView
Displays a list of items.
public class CollectionView : View
{
public static readonly BindableProperty ItemsLayoutProperty;
public IItemsLayout ItemsLayout { get; set; }
public static readonly BindableProperty ItemsSourceProperty;
public IEnumerable ItemsSource { get; set; }
public static readonly BindableProperty ItemTemplateProperty;
public DataTemplate ItemTemplate { get; set; }
public static readonly BindableProperty ItemsUpdatingScrollMode;
publio ItemsUpdatingScrollMode ItemsUpdatingScrollMode { get; set; }
public static readonly BindableProperty HeaderProperty;
public object Header { get; set; }
public static readonly BindableProperty HeaderTemplateProperty;
public DataTemplate HeaderTemplate { get; set; }
public static readonly BindableProperty IsHeaderStickyProperty;
public bool IsHeaderSticky { get; set; }
public static readonly BindableProperty FooterProperty;
public object Footer { get; set; }
public static readonly BindableProperty FooterTemplateProperty;
public DataTemplate FooterTemplate { get; set; }
public static readonly BindableProperty IsFooterStickyProperty;
public bool IsFooterSticky { get; set; }
public static readonly BindableProperty EmptyViewProperty;
public object EmptyView { get; set; }
public static readonly BindableProperty EmptyViewTemplateProperty;
public DataTemplate EmptyViewTemplate { get; set; }
public static readonly BindableProperty GroupDisplayBindingProperty;
public BindingBase GroupDisplayBinding { get; set; }
public static readonly BindableProperty GroupHeaderTemplateProperty;
public DataTemplate GroupHeaderTemplate { get; set; }
public static readonly BindableProperty GroupFooterTemplateProperty;
public DataTemplate GroupFooterTemplate { get; set; }
public static readonly BindableProperty ItemSizingStrategy;
public bool ItemSizingStrategy { get; set; }
public static readonly BindableProperty IsGroupingEnabledProperty;
public bool IsGroupingEnabled { get; set; }
public static readonly BindableProperty SelectionModeProperty;
public SelectionMode SelectionMode { get; set; }
public static readonly BindableProperty SelectedItemProperty;
public object SelectedItem { get; set; }
public static readonly BindableProperty SelectedItemsProperty;
public IList<object> SelectedItems { get; set; }
public static readonly BindableProperty SelectionChangedCommandProperty;
public ICommand SelectionChangedCommand;
public static readonly BindableProperty SelectionChangedCommandParameterProperty;
public object SelectionChangedCommandParameter;
public static readonly BindableProperty RemainingItemsThresholdProperty;
public int RemainingItemsThreshold { get; set; }
public void ScrollTo(object item, object group = null,
ScrollToPosition position = ScrollToPosition.MakeVisible, bool animate = true);
public void ScrollTo(int index, int groupIndex = -1,
ScrollToPosition position = ScrollToPosition.MakeVisible, bool animate = true);
public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
public event EventHandler<EventArgs> RemainingItemsThresholdReached;
}
Properties
API | Description |
---|---|
ItemsLayout | Gets or sets the layout specification for the list. |
ItemSizingStrategy | User hint which can be provided to the control to improve performance. If this is set to MeasureAllItems (the default), each item will be measured individually. In situations where the item size is intended to be uniform, this value can be set to MeasureFirstItem ; only the first item will be measured, and all subsequent items will be given the same size as the first. |
ItemTemplate | Gets or sets the DataTemplate used to display each item. |
ItemsUpdatingScrollMode | Gets or sets a value that specifies scrolling behavior when the items are updated. |
IsGroupingEnabled | Gets or set a value which indicates whether the underlying data should be displayed in groups. |
Header | Gets or sets the string, binding, or view that will be displayed at the top of the control. |
HeaderTemplate | Gets or sets a data template to use to format the Header. |
IsHeaderSticky | Specifies whether the header remains in place as the user scrolls. The default is True |
Footer | Gets or sets the string, binding, or view that will be displayed at the bottom of the control. |
FooterTemplate | Gets or sets a data template to use to format the Footer. |
IsFooterSticky | Specifies whether the footer remains in place as the user scrolls. The default is True |
EmptyView | Gets or sets the string, binding, or view that will be displayed when the ItemsSource is empty. |
EmptyViewTemplate | Gets or sets a data template to use to format the EmptyView. |
GroupHeaderTemplate | Gets or sets a DataTemplate for group headers. |
GroupFooterTemplate | Gets or sets a DataTemplate for group footers.* |
ItemsSource | The list of objects to be displayed in the control. |
SelectionMode | Gets or sets the selection behavior for the control. |
SelectedItem | Gets or sets the selected item for a SelectionMode of Single . If the selected item is removed from the items source, SelectedItem will be set to null . |
SelectedItems | Gets or sets the selected items for a SelectionMode of Multiple . If selected items are removed from the items source, they will be removed from SelectedItems and SelectionChanged will be raised. |
SelectionChangedCommand | Gets or sets the ICommand to execute when the selection changes. |
SelectionChangedCommandParameter | Gets or sets the parameter for the SelectionChangedCommand. |
GroupDisplayBinding | Gets or sets the binding to use for displaying the group header. |
RemainingItemsThreshold | Specifies the threshold of items not yet visible in the CollectionView at which the RemainingItemsThresholdReached event will be raised. The default value is -1, meaning the event will never be raised. A 0, the event will be raised when the final item currently in the ItemsSource is displayed. At values greater than 0, the event will be raised when the ItemsSource currently contains that number of items not yet scrolled to. |
Methods
API | Description |
---|---|
ScrollTo(object item, object group = null, ScrollToPosition position = ScrollToPosition.MakeVisible, bool animate = true) | Scrolls the specified item into view. |
ScrollTo(int index, int groupIndex = -1, ScrollToPosition position = ScrollToPosition.MakeVisible, bool animate = true) | Scrolls the item at the specified index into view. |
Events
API | Description |
---|---|
SelectionChanged | Raised when the SelectedItem or SelectedItems properties change. This includes changes which occur as a result of changing the SelectionMode property. |
RemainingItemsThresholdReached | Raised when the CollectionView is scrolled far enough that only RemainingItemsThreshold items have not been displayed. This event can be handled to load more items. |
Scenarios
Chat Application
The developer has an app which has a chat client. Messages appear at the bottom of the list and scroll up off the screen. How can the developer achieve this UI with the proposed API?
In order to achieve this behavior, the developer should set the CollectionView.ItemsUpdatingScrollMode
property to KeepLastItemInView
. If the user's scroll position is at the end of the CollectionView, the new items will appear at the bottom and will be scrolled into view. If the user's scroll position is elsewhere, the new items will appear at the bottom but the scroll position will remain unchanged.
Snap to closest
The developer's app shows a list of real estate listings. As the user scrolls through the list the scrolling should be smooth until the user stops. When scrolling stops the app's interface should snap the scroll (animated) so that the top most listing is perfectly aligned with the top of the page. The snap should always be in the direction that produces the least amount of motion.
In order to achieve this behavior, the developer should use a ListItemLayout
with the following settings:
Orienation
:Vertical
SnapPointsAlignment
:Start
SnapPointsType
:Mandatory
TableView
The developer wishes to recreate a settings page using a TableView like appearance.
The developer will want to use a ListItemsLayout with a Vertical Orientation. The ItemsTemplate should be set to a DataTemplate which recreates the look and feel of a table cell. If the "settings" interface is meant to address more than one type of setting (e.g., if some cells are meant to toggle a setting on/off and others are meant to navigate to a secondary settings screen), then the developer may wish to create a DataTemplateSelector which implements a DataTemplate for on/off settings (with a toggle switch) and a DataTemplate for navigation (with a TapGesture which pushes the secondary settings page onto the navigation stack).
Infinite Scroll
The developer has an app which shows a "news" feed. The "news" feed has an infinite number of potential items so when the user nears the end of the current loaded set of data the app needs to make an asynchronous call to the server to load more data. It is critical to the app that data be loaded before the user would see either blank space or be stopped from scrolling within the limits of network latency/bandwidth. How can the developer achieve this with the proposed API?
To achieve this, the developer should set the RemainingItemsThreshold
property and handle the RemainingItemsThresholdReached
event. When the event is raised, the handler can make the asynchronous call to the server to load more data into the underlying ItemsSource
.