Showing posts with label Drop. Show all posts
Showing posts with label Drop. Show all posts

Friday, 20 February 2015

Xamarin.Forms ListView Drag and Drop to Reorder

We are currently looking at ways to add some UX improvements to our application so I thought I would investigate drag and drop on ListView.

There doesn't seem to be anything out of the box yet in Xamarin.Forms so I did a search and found the following :-

http://xamurais.com/drag-and-drop-entre-listview-en-xamarin-android/

This sample was written in classic Xamarin.Android, however I was looking for a Xamarin.Forms implementation. This sample however provided me with the ground work for the sample I propose below.

My sample is only targeting Android at the moment.

My implementation consists of a ViewCellRenderer, this allows you to define a ListView with an ItemTemplate, so that you can bind your ListView to more than a List.

MyViewCellRenderer.cs :-

using System.Collections;
using Android.Content;
using Android.Views;
using ListViewDragDropSample.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using View = Android.Views.View;

[assembly: ExportRenderer(typeof(ViewCell), typeof(MyViewCellRenderer))]
namespace ListViewDragDropSample.Droid
{
 public class MyViewCellRenderer : ViewCellRenderer
 {
  public ListView ParentListView { get; set; }

  public IList Items { get; set; }

  protected override View GetCellCore(Cell item, View convertView, ViewGroup parent, Context context)
  {
   ParentListView = item.ParentView as ListView;

   if (ParentListView != null)
   {
    Items = ParentListView.ItemsSource as IList;
   }

   var cellcore = base.GetCellCore(item, convertView, parent, context);

   cellcore.Drag -= CellcoreOnDrag;
   cellcore.Drag += CellcoreOnDrag;

   return cellcore;
  }

  private void CellcoreOnDrag(object sender, View.DragEventArgs args)
  {
   ViewGroup = sender as ViewGroup;

   if (ViewGroup != null)
   {
    ListView = ViewGroup.Parent.Parent as Android.Widget.ListView;
   }

   switch (args.Event.Action)
   {
    case DragAction.Started:
     args.Handled = true;
     break;

    case DragAction.Entered:
     args.Handled = true;

     if (ListView != null)
     {
      if (FirstIndex == -1)
      {
       FirstIndex = ListView.IndexOfChild(ViewGroup.Parent as View);
      }
     }

     break;

    case DragAction.Exited:
     args.Handled = true;
     break;

    case DragAction.Drop:
     args.Handled = true;

     if (SecondIndex == -1)
     {
      SecondIndex = ListView.IndexOfChild(ViewGroup.Parent as View);
     }

     if (FirstIndex != -1)
     {
      var firstItem = Items[FirstIndex];

      if (firstItem != null)
      {
       Items.RemoveAt(FirstIndex);
       Items.Insert(SecondIndex, firstItem);

       ParentListView.ItemsSource = null;
       ParentListView.ItemsSource = Items;
      }
     }

     FirstIndex = -1;
     SecondIndex = -1;

     break;
    case DragAction.Ended:
     args.Handled = true;
     break;
   }
  }

  public Android.Widget.ListView ListView { get; set; }

  public ViewGroup ViewGroup { get; set; }

  private static int _firstIndex = -1;
  private static int _secondIndex = -1;

  public static int FirstIndex
  {
   get { return _firstIndex; }
   set { _firstIndex = value; }
  }
  public static int SecondIndex
  {
   get { return _secondIndex; }
   set { _secondIndex = value; }
  }
 }
}

MyListViewRenderer.cs :-

using Android.Content;
using ListViewDragDropSample.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(ListView), typeof(MyListViewRenderer))]
namespace ListViewDragDropSample.Droid
{
 public class MyListViewRenderer : ListViewRenderer
 {
  protected override void OnElementChanged(ElementChangedEventArgs e)
  {
   base.OnElementChanged(e);

   Control.ItemLongClick += (s, args) =>
   {
    ClipData data = ClipData.NewPlainText("List", args.Position.ToString());
    MyDragShadowBuilder myShadownScreen = new MyDragShadowBuilder(args.View);
    args.View.StartDrag(data, myShadownScreen, null, 0);
   };
  }
 }
}

MainPage.xaml :-

 
  
   
    
     
      
    
   
  
 


MainPage.xaml.cs :-

using System.Collections.Generic;
using Xamarin.Forms;

namespace ListViewDragDropSample
{
 public partial class MainPage : ContentPage
 {
  public MainPage()
  {
   InitializeComponent();

   Items = new List();

   for (int i = 1; i < 11; i++)
   {
    Items.Add(new Item()
    {
     Title = "Title : " + i,
     Description = "Description : " + i,
    });
   }

   BindingContext = this;
  }

  public List Items { get; set; }
 }
}



MyDragShadowBuilder.cs :-
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Views;

namespace ListViewDragDropSample.Droid
{
 public class MyDragShadowBuilder : View.DragShadowBuilder
 {
  private Drawable shadow;

  public MyDragShadowBuilder(View v)
   : base(v)
  {
   v.DrawingCacheEnabled = true;
   Bitmap bm = v.DrawingCache;
   shadow = new BitmapDrawable(bm);
   shadow.SetColorFilter(Color.ParseColor("#4EB1FB"), PorterDuff.Mode.Multiply);
  }

  public override void OnProvideShadowMetrics(Point size, Point touch)
  {
   int width = View.Width;
   int height = View.Height;
   shadow.SetBounds(0, 0, width, height);
   size.Set(width, height);
   touch.Set(width / 2, height / 2);
  }

  public override void OnDrawShadow(Canvas canvas)
  {
   base.OnDrawShadow(canvas);
   shadow.Draw(canvas);
  }
 }
}


And finally the Item.cs :-

namespace ListViewDragDropSample
{
 public class Item
 {
  public string Title { get; set; }
  public string Description { get; set; }
 }
}

The complete sample is here :-

http://www.smartmobiledevice.co.uk/Samples/Xamarin/ListViewDragDropSample.zip