Skip to content

HorizontalListView Grid And Carousel

roubachof edited this page Apr 24, 2020 · 3 revisions

You can find the blog post on www.sharpnado.com/a-real-horizontal-list-view/.

Silly! app

The HorizontalListView is presented in the Silly! app in the following repository:

https://github.com/roubachof/Xamarin-Forms-Practices

If you want to know how to use it, it's the best place to start.

Linear layout

public HorizontalListViewLayout ListLayout { get; set; } = HorizontalListViewLayout.Linear;

By default the layout is in Linear mode, which means you will have only one row. You can specify the ItemWidth and ItemHeight. You can also specify ItemSpacing and CollectionPadding.

<renderedViews:HorizontalListView Grid.Row="3"
                                  Margin="-16,8"
                                  CollectionPadding="8,8"
                                  ItemSpacing="8"
                                  ItemHeight="144"
                                  ItemWidth="144"
                                  ItemsSource="{Binding SillyPeopleLoader.Result}"
                                  SnapStyle="Center">
    <renderedViews:HorizontalListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <views:SillySquareCell effects:TapCommandEffect.Tap="{Binding OnItemTappedCommand}"
                                       effects:TapCommandEffect.TapParameter="{Binding .}"
                                       effects:ViewEffect.TouchFeedbackColor="{StaticResource Accent}" />
            </ViewCell>
        </DataTemplate>
    </renderedViews:HorizontalListView.ItemTemplate>
</renderedViews:HorizontalListView>

A HorizontalListView with SnapStyle=Center and ItemWidth/ItemHeight set.

As you can see TapCommand and TouchFeedbackColor (aka Ripple) are brought to you by the awesome effects created by mrxten (https://github.com/mrxten/XamEffects). The class effects are directly integrated in the Sharpnado projects so you don't have to reference another nuget package.

ColumnCount property

You can also decide to just specify the number of column you want, the ColumnCount property, and the ItemWidth will be computed for you.

<renderedViews:HorizontalListView Grid.Row="3"
                                  Margin="-16,8"
                                  CollectionPadding="8,8"
                                  ItemSpacing="8"
                                  ColumnCount="2"
                                  ItemsSource="{Binding SillyPeopleLoader.Result}"
                                  SnapStyle="Start">
    ...
</renderedViews:HorizontalListView>

A HorizontalListView with ColumnCount=2.

Carousel Layout

You can set ListLayout to Carousel. In this mode you can't specify ItemWidth (obviously). If you don't specify the ItemHeight, it will be automatically computed for you.

<renderedViews:HorizontalListView Grid.Row="3"
                                  Margin="-16,8"
                                  CollectionPadding="8,8"
                                  ItemSpacing="8"
                                  ListLayout="Carousel"
                                  ItemsSource="{Binding SillyPeopleLoader.Result}"
                                  SnapStyle="Center">
    ...
</renderedViews:HorizontalListView>

A HorizontalListView with ListLayout=Carousel.

Grid Layout

If you set the ListLayout property to Grid, you will have access to the same properties.

<renderedViews:HorizontalListView CollectionPadding="16"
                                  ItemSpacing="8"
                                  EnableDragAndDrop="True"
                                  ItemWidth="110"
                                  ItemHeight="120"
                                  ItemsSource="{Binding SillyPeople}"
                                  ListLayout="Grid">
    <renderedViews:HorizontalListView.ItemTemplate>
        <DataTemplate>
            <renderedViews:DraggableViewCell x:Name="DraggableViewCell">
                <ContentView>
                    <renderedViews:MaterialFrame Margin="4"
                                                 Padding="{StaticResource StandardThickness}"
                                                 Elevation="4">

                        <Frame.Triggers>
                            <DataTrigger Binding="{Binding Source={x:Reference DraggableViewCell}, Path=IsDragAndDropping}"
                                         TargetType="renderedViews:MaterialFrame"
                                         Value="True">
                                <Setter Property="Elevation" Value="8" />
                            </DataTrigger>
                        </Frame.Triggers>

                        <Grid ColumnSpacing="0" RowSpacing="0">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="2*" />
                                <RowDefinition Height="*" />
                            </Grid.RowDefinitions>
                            <abstractions:CircleImage Grid.Row="0"
                                                      Style="{StaticResource SmallAvatar}"
                                                      Aspect="AspectFill"
                                                      Source="{Binding ImageUrl}" />
                            <Label Grid.Row="1"
                                   Margin="{StaticResource MediumTopThickness}"
                                   Style="{StaticResource TextSmallCaption}"
                                   HorizontalTextAlignment="Center"
                                   Text="{Binding Name}" />
                        </Grid>
                    </renderedViews:MaterialFrame>
                </ContentView>
            </renderedViews:DraggableViewCell>
        </DataTemplate>
    </renderedViews:HorizontalListView.ItemTemplate>
</renderedViews:HorizontalListView>

The nuget package comes also with a MaterialFrame view with Elevation property. Some code has been taken from Alex Dunn work.

A Grid ListLayout with padding and item spacing.

You can use the IsDragAndDropping property of the DraggableViewCell to achieve an Elevation effect while dragging your view with a simple DataTrigger.

ColumnCount property

The ColumnCount property works also with the grid layout.

<renderedViews:HorizontalListView CollectionPadding="8"
                                  ItemSpacing="8"
                                  EnableDragAndDrop="True"
                                  ColumnCount="1"
                                  ItemHeight="120"
                                  ItemsSource="{Binding SillyPeople}"
                                  ListLayout="Grid">

A Grid ListLayout with ColumnCount=1.

Infinite Loading

You can achieve infinite loading really easily by using the Paginator component, and bind it to the InfiniteListLoader property. All is explained here:

https://www.sharpnado.com/paginator-platform-independent/

Drag and drop

If you want to have both drag and drop enabled and still be able to tap the item, you need to use the TapCommand instead of the TapCommandEffect. It's less nice since you won't have the nice color ripple, but it will work :)

The only thing you have to do to enable drag and drop is setting EnableDragAndDrop to true.

Remark: You don't have to inherit from DraggableViewCell, any ViewCell can be dragged.

DraggableViewCell

The DraggableViewCell is useful for using triggers on the IsDragAndDropping property (changing its background color or elevation during drag and drop for example).

You can also disable the drag and drop for certain cells thanks to the IsDraggable property.

Others properties

Properties available with both layout mode

public static readonly BindableProperty ListLayoutProperty = BindableProperty.Create(
    nameof(ListLayout),
    typeof(HorizontalListViewLayout),
    typeof(HorizontalListView),
    HorizontalListViewLayout.Linear);

public static readonly BindableProperty TapCommandProperty = BindableProperty.Create(
    nameof(TapCommand),
    typeof(ICommand),
    typeof(HorizontalListView));

public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
    nameof(ItemsSource),
    typeof(IEnumerable),
    typeof(HorizontalListView),
    default(IEnumerable<object>),
    BindingMode.TwoWay);

public static readonly BindableProperty InfiniteListLoaderProperty = BindableProperty.Create(
    nameof(ItemsSource),
    typeof(IInfiniteListLoader),
    typeof(HorizontalListView));

public static readonly BindableProperty ScrollBeganCommandProperty = BindableProperty.Create(
    nameof(ScrollBeganCommand),
    typeof(ICommand),
    typeof(HorizontalListView));

public static readonly BindableProperty ScrollEndedCommandProperty = BindableProperty.Create(
    nameof(ScrollEndedCommand),
    typeof(ICommand),
    typeof(HorizontalListView));

public static readonly BindableProperty CurrentIndexProperty = BindableProperty.Create(
    nameof(CurrentIndex),
    typeof(int),
    typeof(HorizontalListView),
    defaultValue: 0,
    defaultBindingMode: BindingMode.TwoWay);

public static readonly BindableProperty VisibleCellCountProperty = BindableProperty.Create(
    nameof(VisibleCellCount),
    typeof(int),
    typeof(HorizontalListView),
    defaultValue: 0,
    defaultBindingMode: BindingMode.TwoWay);

public static readonly BindableProperty DisableScrollProperty = BindableProperty.Create(
    nameof(DisableScroll),
    typeof(bool),
    typeof(HorizontalListView),
    defaultValue: false,
    defaultBindingMode: BindingMode.TwoWay);


/// In certain scenarios, the first scroll of the list can be smoothen
/// by pre-building some views.
public int ViewCacheSize { get; set; } = 0;

public SnapStyle SnapStyle { get; set; } = SnapStyle.None;

public int ColumnCount { get; set; } = 0;

public ScrollSpeed ScrollSpeed { get; set; } = ScrollSpeed.Normal;

Properties available with Grid ListLayout

public bool EnableDragAndDrop { get; set; } = false;

public static readonly BindableProperty DragAndDropEndedCommandProperty = BindableProperty.Create(
    nameof(DragAndDropEndedCommand),
    typeof(ICommand),
    typeof(HorizontalListView));

public static readonly BindableProperty IsDragAndDroppingProperty = BindableProperty.Create(
    nameof(IsDragAndDropping),
    typeof(bool),
    typeof(HorizontalListView),
    defaultValue: false);

Some implementation details

Android

The Android renderer is implemented with a RecyclerView. Padding and item spacing is computed by an extension of ItemDecoration. While column computing and item distribution is achieved by a custom GridLayoutManager. The Snap to first item is implemented with a custom LinearSnapHelper. Drag and drop is handled by an ItemTouchHelper.Callback.

iOS

The iOS renderer is implemented by a UICollectionView. Padding and item spacing are natively provided by the UICollectionViewFlowLayout. Snap to Center item is brought by a little trick on DecelerationEnded callback. Darg and drop is handled by a UILongPressGestureRecognizer followed by calls to the xxxInteractiveMovementxxx methods.