March 4th, 2021

CollectionView Drag & Drop Item Reordering with Gesture Recognizers

alexeystrakh
Software Developer Engineer

Items reordering in a list-based control is a handy feature and it is used in various use cases. From simple scenarios like configuring settings, defining tabs order to complex scenarios like managing user content, and reordering images or email. With the recent Xamarin.Forms 5.0 release, Drag and Drop gesture recognizers were introduced and enable you to unlock these scenarios in your app. In this post I will guide you through everything you need to add drag & drop to your CollectionView.

Drag & Drop Gesture Recognizers

In previous posts you may have seen how drag and drop can be used with any control with the new gesture recognizers in Xamarin.Forms. However, views inside CollectionView organized by the control itself, are special and include caching and virtualization. These optimizations can break if you start moving views around. Don’t worry those as gesture recognizers can still be used to work with the views indirectly. The recognizers have useful properties to bind them to commands with parameters:

    <DragGestureRecognizer
        ...
        DragStartingCommand="{Binding ...}"
        DragStartingCommandParameter="{Binding ...}" />

    <DropGestureRecognizer
        ...
        DragOverCommand="{Binding ...}"
        DragOverCommandParameter="{Binding ...}"
        DragLeaveCommand="{Binding ...}"
        DragLeaveCommandParameter="{Binding ...}"
        DropCommand="{Binding ...}"
        DropCommandParameter="{Binding ...}" />

This is enough to track user requests to move one item to some other location. The item here is an element of a collection, bound to the CollectionView control. And if you can change the order of the items in the underlying collection, you can do the same with the new gesture recognizers.

Reorder Items

First, you need a view model with a collection, where you want to reorder items. The items reordering logic is defined in C# with no UI-dependent code. A basic algorithm that takes one item of a collection and moves it in front of another item of the same collection. You need a few properties in the view model and the core reordering logic:

    public ICommand ItemDragged { get; }
    public ICommand ItemDraggedOver { get; }
    public ICommand ItemDragLeave { get; }
    public ICommand ItemDropped { get; }


    private async Task OnItemDropped(ItemViewModel item)
    {
        var itemToMove = items.First(i => i.IsBeingDragged);
        var itemToInsertBefore = item;
        if (itemToMove == null || itemToInsertBefore == null || itemToMove == itemToInsertBefore)
            return;

        var categoryToMoveFrom = GroupedItems.First(g => g.Contains(itemToMove));
        categoryToMoveFrom.Remove(itemToMove);

        var categoryToMoveTo = GroupedItems.First(g => g.Contains(itemToInsertBefore));
        var insertAtIndex = categoryToMoveTo.IndexOf(itemToInsertBefore);
        itemToMove.Category = categoryToMoveFrom.Name;
        categoryToMoveTo.Insert(insertAtIndex, itemToMove);
        itemToMove.IsBeingDragged = false;
        itemToInsertBefore.IsBeingDraggedOver = false;
    }

Next, to make it closer to a real-world example, the items in the CollectionView are grouped by category. And the algorithm above can drag items between categories:

    public class ItemViewModel : ObservableObject
    {
        public string Category { get; set; }
        public string Title { get; set; }

        private bool isBeingDragged;
        public bool IsBeingDragged
        {
            get { return isBeingDragged; }
            set { SetProperty(ref isBeingDragged, value); }
        }

        private bool isBeingDraggedOver;
        public bool IsBeingDraggedOver
        {
            get { return isBeingDraggedOver; }
            set { SetProperty(ref isBeingDraggedOver, value); }
        }
    }

Finally, you need to disable any Drag and Drop logic defined by the gestures using the Drop event. This ensures that only the logic defined by the view model is used to reorder items:

    private void DropGestureRecognizer_Drop_Collection(System.Object sender, Xamarin.Forms.DropEventArgs e)
    {
        e.Handled = true;
    }

xamarinforms-collectionview-groupeditems-reordering.gif
]5 xamarinforms collectionview grouped items reordering

Summary

You can reorder items in a CollectionView by reordering items in the underlying collection bound to the CollectionView. The Drag and Drop gesture recognizers can help you to trigger the reordering logic, defined by a view model, that holds the collection. This solution doesn’t use any native code, the items reordering logic is defined in a view model while the gesture recognizers take advantage of the MVVM to communicate with that logic. You can also customize the look and feel for an item being dragged and a targeted area, everything in XAML with HotReload enabled. You can use this solution today with any collection-based control, taking this sample repo as a starting point.

Useful links

  1. Collection View items reordering sample
  2. Enhancement Support drag-and-drop reordering in CollectionView
  3. Drag and Drop gesture recognizers docs
  4. Drag and Drop gesture recognizers sample
  5. CollectionView control
  6. CollectionView grouping

Author

alexeystrakh
Software Developer Engineer

I'm a Software Developer Engineer for Mobile Customer Advisory Team, aka MobCAT, at Microsoft, focused on Xamarin. A .NET developer since 2005, and a mobile app developer since 2011. My goal is to make sure that all the developers have amazing and joyful experiences working with the Microsoft developer tools and the tools are waiting for developers and not vice versa.

7 comments

Discussion is closed. Login to edit/delete existing comments.

  • jm dessaintes · Edited

    Works ok if source and destination CollectionView items are on the screen. If destination is not on the screen, no scroll is done, hence no drag and drop possible !
    How can automatic scroll in the CollectionView be done during a Drag and Drop?

    • alexeystrakhMicrosoft employee Author

      I have checked on the sample code provided and was able to drag&drop to a destination no visible when the drag is initiated. To achieve that, initiate a drag and then move the item to the edge (but do not touch the edge), after a short delay the scroll is engaged and you will be able to see the rest of the list.

      • jm dessaintes

        Thank you for responding.
        But it scrolls only 1 or 2 items on the emulator (Pixel 2 Q 10.0 Api 29) and on my Oneplus 5 !

  • George Leithead

    The article and example code is great. Thanks for creating. The Drag and Drop doesn't work too well when the items exceed the screen, as the scroll engages. Makes it tough to use for large lists.
    Do you know of any examples where the CollectionView is grouped and each group heading is horizontally listed instead of vertically (as in this example)? Or other examples where the user can drag and drop...

    Read more
    • alexeystrakhMicrosoft employee Author

      This example relies on Drag And Drop Gesture Recognizers, which can be used to grad an item from one CollectionView to another. In your case, only the logic to move items will change, taking items from one item source and inserting the items to the source of another item. The rest will be handled automatically by data bindings and CollectionView.

  • Rand Random

    No fan of the animation when you release the item.
    Instead of it just dropping it where you released the pointer, it goes back to the original state just to animate the move to new position.

    • alexeystrakhMicrosoft employee Author

      I agree that animation is not perfect here. In fact, the animation is not addressed in this sample at all, just whatever covered by default animation when you add/remove items. You have complete control of the animation, and the current simple algorithm, once the item is dropped, removes the item from one place and inserts it in another. To optimize it, instead of removing the item, create a copy, insert it in the desired location...

      Read more