Item Reordering in Xamarin ListView (SfListView)
14 Aug 202324 minutes to read
The SfListView allows reordering by dragging and dropping items. It supports displaying the customized view in a template while dragging the item. It can be enabled by setting the SfListView.DragStartMode property to OnHold
. The drag and drop options are listed as follows:
- None: Disables drag and drop. This is the default value.
- OnHold: Allows dragging and dropping by holding the item.
- OnDragIndicator: Allows dragging and dropping by loading the DragIndicatorView within SfListView.ItemTemplate.
NOTE
The GridLayout does not support drag and drop.
The drag and drop scenarios are as follows:
- Items can be reordered to any position by auto-scrolling.
- Items can be reordered in same group or in other groups but, no groups can be added to other groups.
- Groups, header, and footer cannot be reordered.
To enable drag and drop using ‘OnHold’, follow the code example:
<ContentPage xmlns:syncfusion="clr-namespace:Syncfusion.ListView.XForms;assembly=Syncfusion.SfListView.XForms">
<syncfusion:SfListView x:Name="listView"
ItemsSource="{Binding ToDoList}"
DragStartMode="OnHold"
BackgroundColor="#FFE8E8EC"
ItemSize="60" />
</ContentPage>
listView.DragStartMode = DragStartMode.OnHold;
To enable drag and drop using both ‘OnHold’ and ‘OnDragIndicator’, follow the code example:
<ContentPage xmlns:syncfusion="clr-namespace:Syncfusion.ListView.XForms;assembly=Syncfusion.SfListView.XForms">
<syncfusion:SfListView x:Name="listView"
ItemsSource="{Binding ToDoList}"
DragStartMode="OnHold, OnDragIndicator"
BackgroundColor="#FFE8E8EC"
ItemSize="60" />
</ContentPage>
listView.DragStartMode = DragStartMode.OnHold | DragStartMode.OnDragIndicator;
NOTE
Reordering changes made only in view, and not in underlying collection. Thus, the changes will be reverted when performing sorting, grouping, or any other operation that refreshes view. You can update underlying collection by setting UpdateSource to
true
.
Drag indicator view
To drag and drop the items by DragIndicatorView, set the SfListView.DragStartMode property to OnDragIndicator
. To display the dragging item, define any custom user interface(UI) in DragIndicatorView
.
NOTE
You must set the SfListView instance as reference to the ListView property in
DragIndicatorView
.
<ContentPage xmlns:syncfusion="clr-namespace:Syncfusion.ListView.XForms;assembly=Syncfusion.SfListView.XForms">
<syncfusion:SfListView x:Name="listView"
ItemsSource="{Binding ToDoList}"
DragStartMode="OnDragIndicator"
BackgroundColor="#FFE8E8EC"
ItemSize="60">
<syncfusion:SfListView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<Label x:Name="textLabel" Text="{Binding Name}" Grid.Column="1" FontSize="15" TextColor="#333333" />
<syncfusion:DragIndicatorView Grid.Column="2" ListView="{x:Reference listView}"
HorizontalOptions="Center"
VerticalOptions="Center">
<Grid Padding="10, 20, 20, 20">
<Image Source="DragIndicator.png" />
</Grid>
</syncfusion:DragIndicatorView>
</Grid>
</Grid>
</DataTemplate>
</syncfusion:SfListView.ItemTemplate>
</syncfusion:SfListView>
</ContentPage>
listView.ItemTemplate = new DataTemplate(() =>
{
var grid = new Grid();
var name = new Label
{
FontSize = 15,
VerticalOptions = LayoutOptions.Center
};
name.SetBinding(Label.TextProperty, new Binding("Name"));
var indicatorGrid = new Grid()
{
Padding = new Thickness(10, 20, 20, 20),
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Center
};
var dragIndicatorView = new DragIndicatorView() { ListView = this.listView };
var indicator = new Image() { Source = "DragIndicator.png" };
indicatorGrid.Children.Add(indicator);
dragIndicatorView.Content = indicatorGrid;
grid.Children.Add(name);
grid.Children.Add(dragIndicatorView, 1, 0);
return grid;
});
The screenshot shows the output of the reordering items by drag and drop. Download the entire source code from GitHub here.
Drag item customization
By defining the SfListView.DragItemTemplate of the SfListView
, displays the custom User Interface(UI) when performing drag and drop operations. The template can be defined either in code or XAML.
NOTE
If
BackgroundColor
is set toDragItemTemplate
or DragIndicatorView, setInputTransparent
totrue
. Since, dragging does not happen when performing byDragIndicatorView
in UWP.
<ContentPage xmlns:syncfusion="clr-namespace:Syncfusion.ListView.XForms;assembly=Syncfusion.SfListView.XForms">
<syncfusion:SfListView x:Name="listView"
ItemsSource="{Binding ToDoList}"
DragStartMode="OnHold"
BackgroundColor="#FFE8E8EC"
ItemSize="60">
<syncfusion:SfListView.DragItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Label x:Name="textLabel" Text="{Binding Name}" FontSize="15" />
</Grid>
</DataTemplate>
</syncfusion:SfListView.DragItemTemplate>
</syncfusion:SfListView>
</ContentPage>
listView.ItemTemplate = new DataTemplate(() => {
var grid = new Grid();
var name = new Label { FontSize = 15 };
name.SetBinding(Label.TextProperty, new Binding("Name"));
grid.Children.Add(name);
return grid;
});
Event
The ItemDragging event is raised while dragging and dropping the item in the SfListView. The ItemDraggingEventArgs has the following members which provides the information for the ItemDragging
event:
-
Action: Returns the drag
Action
such as start, dragging, and drop. - Bounds: Return bounds of drag item when dragging and dropping.
-
Handled: If this member is set to
true
, dragging can be handled. It is applicable only ifAction
isDragging
. - ItemData: Returns the underlying data of the dragging item.
- NewIndex: Returns the item index of the DataSource.DisplayItems where dragging item is going to be dropped.
-
OldIndex: Returns the item index of the DataSource.DisplayItems where dragging item started. The OldIndex and NewIndex will be same if
Action
isStart
. - Position: Returns the touch position of the drag item from screen coordinates.
Auto scroll options
Auto scroll margin
To adjust auto scroll margin, set a value to the ScrollMargin property of AutoScroller to enable auto-scrolling while dragging. The default value is 15
. Auto-scrolling will be enabled when reaching ScrollMargin
from view bounds while dragging.
To disable auto-scrolling, set the value to 0
for ScrollMargin
.
this.listView.AutoScroller.ScrollMargin = 20;
Auto scroll interval
To adjust auto-scroll interval while dragging, set the Interval property of AutoScroller. The default value is 150 milliseconds
.
this.listView.AutoScroller.Interval = new TimeSpan(0, 0, 0, 0, 200);
Disable outside scroll
To disable auto-scroll when dragging item moves outside the SfListView
while dragging, set the AllowOutsideScroll property of AutoScroller to false
. The default value is true
.
this.listView.AutoScroller.AllowOutsideScroll = false;
Disable dragging for particular item
To disable dragging for a particular item, handle the ItemDragging event based on the conditions of Action event argument.
private void ListView_ItemDragging(object sender, ItemDraggingEventArgs e)
{
// Disable the dragging for 4th item.
if (e.Action == DragAction.Start && e.NewIndex == 3)
e.Cancel = true;
}
Cancel dropping for the dragged item
To cancel dropping for the dragged item, handle the ItemDragging event based on the conditions of Action event argument.
using Syncfusion.ListView.XForms.Control.Helpers;
private void ListView_ItemDragging(object sender, ItemDraggingEventArgs e)
{
// Cancel the dropping if drop the drag item into out of view.
var listView = sender as ListView;
var totalExtent = listView.GetVisualContainer().Bounds.Bottom;
if (e.Action == DragAction.Drop && (e.Bounds.Y < -30 || e.Bounds.Bottom > totalExtent + 40))
e.Cancel = true;
}
Reorder the underlying collection
The underlying collection can be reordered directly by setting the UpdateSource property to true
. The default value is false
.
<ContentPage xmlns:syncfusion="clr-namespace:Syncfusion.ListView.XForms;assembly=Syncfusion.SfListView.XForms">
<syncfusion:SfListView x:Name="listView"
ItemsSource="{Binding ToDoList}"
DragStartMode="OnHold"
BackgroundColor="#FFE8E8EC"
ItemSize="60">
<syncfusion:SfListView.DragDropController>
<syncfusion:DragDropController
UpdateSource="True">
</syncfusion:DragDropController>
</syncfusion:SfListView.DragDropController>
</syncfusion:SfListView>
</ContentPage>
this.listView.DragDropController.UpdateSource = true;
We can able to update collection even when UpdateSource
is false
. Like, user can decide where dragged item should be dropped actually by handling the ItemDragging
event with DragAction.Drop.
private void ListView_ItemDragging(object sender, ItemDraggingEventArgs e)
{
if (e.Action == DragAction.Drop)
{
ViewModel.ToDoList.MoveTo(1, 5);
}
}
NOTE
Underlying collection will not be updated when any data operation like sorting or grouping is performed. The order will be maintained only in
DisplayItems
of data source. When drag and drop an item between groups, the value of the property in which grouping is performed is updated in data object.
Delete item when dropping in particular view
To delete the dragged item when dropping into a particular view, handle the ItemDragging event based on the conditions of Action and Bounds
event arguments.
To delete the dragged item from the underlying collection when dropping into delete icon, follow the code example. It will enable or disable whenever drag started, and dropped by IsVisible
property in ViewModel.
<ContentPage xmlns:syncfusion="clr-namespace:Syncfusion.ListView.XForms;assembly=Syncfusion.SfListView.XForms">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid BackgroundColor="#2196F3">
<Label Text="To Do Items" x:Name="headerLabel" TextColor="White" FontAttributes="Bold" VerticalOptions="Center" HorizontalOptions="Center" />
<StackLayout x:Name="stackLayout" IsVisible="{Binding IsVisible}" Orientation="Horizontal" VerticalOptions="Center" HorizontalOptions="Center">
<Image Source="Delete.png" HeightRequest="30" WidthRequest="30" />
<Label x:Name="deleteLabel" Text="Delete Item" FontAttributes="Bold" TextColor="White" />
</StackLayout>
</Grid>
<syncfusion:SfListView x:Name="listView" Grid.Row="1"
ItemsSource="{Binding ToDoList}"
DragStartMode="OnHold"
BackgroundColor="#FFE8E8EC"
ItemSize="60" />
</Grid>
</ContentPage>
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(40) });
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
var grid1 = new Grid();
var headerLabel = new Label()
{
Text = "To Do Items"
};
var stackLayout = new StackLayout();
stackLayout.SetBinding(StackLayout.IsVisibleProperty, new Binding("IsVisible"));
var image = new Image() { Source = "Delete.png" };
var deleteLabel = new Label() { Text = "DeleteItem" };
stackLayout.Children.Add(image);
stackLayout.Children.Add(deleteLabel);
grid1.Children.Add(headerLabel);
grid1.Children.Add(stackLayout);
var listView = new SfListView()
{
DragStartMode = DragStartMode.OnHold,
ItemSize = 60,
SelectionMode = SelectionMode.None,
BackgroundColor = Color.FromHex("#FFE8E8EC")
};
listView.SetBinding(ListView.ItemsSourceProperty, new Binding("ToDoList"));
grid.Children.Add(grid1);
grid.Children.Add(listView, 0, 1);
}
}
private async void ListView_ItemDragging(object sender, ItemDraggingEventArgs e)
{
var viewModel = this.listView.BindingContext as ViewModel;
if (e.Action == DragAction.Start)
{
viewModel.IsVisible = true;
this.stackLayout.Opacity = 0.25;
}
if(e.Action == DragAction.Dragging)
{
var position = new Point(e.Position.X - this.listView.Bounds.X, e.Position.Y - this.listView.Bounds.Y);
if (this.stackLayout.Bounds.Contains(position))
this.deleteLabel.TextColor = Color.Red;
else
this.deleteLabel.TextColor = Color.White;
}
if(e.Action == DragAction.Drop)
{
var position = new Point(e.Position.X - this.listView.Bounds.X, e.Position.Y - this.listView.Bounds.Y);
if (this.stackLayout.Bounds.Contains(position))
{
await Task.Delay(100);
viewModel.ToDoList.Remove(e.ItemData as ToDoItem);
}
viewModel.IsVisible = false;
this.deleteLabel.TextColor = Color.White;
this.headerLabel.IsVisible = true;
}
}
Download the sample from GitHub here.
Skip dragging item into another group
To skip dragging from one group to another group, handle the ItemDragging event based on the conditions of Action and Bounds
event arguments.
NOTE
While auto-scrolling, dragging item cannot be skipped.
Skip the dragging item by bounds of dragging item, and bounds of current and next group item.
using Syncfusion.ListView.XForms.Control.Helpers;
private async void ListView_ItemDragging(object sender, ItemDraggingEventArgs e)
{
if (e.Action == DragAction.Dragging)
{
var currentGroup = this.GetGroup(e.ItemData);
var container = this.ListView.GetVisualContainer();
var groupIndex = this.ListView.DataSource.Groups.IndexOf(currentGroup);
var nextGroup = (groupIndex + 1 < this.ListView.DataSource.Groups.Count) ? this.ListView.DataSource.Groups[groupIndex + 1] : null;
ListViewItem groupItem = null;
ListViewItem nextGroupItem = null;
foreach (ListViewItem item in container.Children)
{
if (item.BindingContext == null || !item.Visibility)
continue;
if (item.BindingContext.Equals(currentGroup))
groupItem = item;
if (nextGroup != null && item.BindingContext.Equals(nextGroup))
nextGroupItem = item;
}
if (groupItem != null && e.Bounds.Y <= groupItem.Y + groupItem.Height || nextGroupItem != null && (e.Bounds.Y + e.Bounds.Height >= nextGroupItem.Y))
e.Handled = true;
}
}
private GroupResult GetGroup(object itemData)
{
GroupResult itemGroup = null;
foreach (var item in this.listView.DataSource.DisplayItems)
{
if (item is GroupResult)
itemGroup = item as GroupResult;
if (item == itemData)
break;
}
return itemGroup;
}
Download sample from GitHub here
Drag and drop customization
Adjust drag item axis
To adjust drag item coordinates (X and Y) while dragging, returns true from virtual method CanAdjustDragItemAxis of DragDropController. By default, Y coordinates can be adjusted if SfListView.Orientation is Vertical
, and X coordinates can be adjusted if Orientation
is Horizontal
.
this.listView.DragDropController = new DragDropControllerExt(this.listView);
public class DragDropControllerExt : DragDropController
{
public DragDropControllerExt(SfListView listView) : base(listView)
{
}
protected override bool CanAdjustDragItemAxis()
{
return true;
}
}
Layout item on dragging
In the SfListView, layout the ListViewItem with different animations, and time on dragging by virtual method OnLayoutItem.
this.listView.DragDropController = new DragDropControllerExt(this.listView);
public class DragDropControllerExt : DragDropController
{
public DragDropControllerExt(SfListView listView) : base(listView)
{
}
protected override Task<bool> OnLayoutItem(View element, Rectangle rect)
{
return element.LayoutTo(rect, 250, Easing.BounceIn);
}
}
NOTE
You can refer to our Xamarin ListView feature tour page for its groundbreaking feature representations. You can also explore our Xamarin.Forms ListView example to know how to render set of data items with Xamarin.Forms views or custom templates.
See also
How to drag and drop an item from one to another listview in Xamarin.Forms
How to show or hide the drag indicator like iOS listview
How to retrieve the dragged item index in ViewModel command with the Prism framework in Xamarin.Forms ListView (SfListView)
How to show or hide drag indicator based on the DragStartMode in Xamarin.Forms ListView (SfListView)
How to get the dropped item group in Xamarin.Forms ListView (SfListView)
How to handle button action of ListView item when dragging in Xamarin.Forms (SfListView)