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
HI,
ReplyDeleteThank for your help!Actually I used your source code but the thing is ,its not working while lots of data , lets add some 25 data and try to swap ,it will not work
it is works more than 25 rows. but, index is wrang.
Deleteso, I fixed source code by:
MyViewCellRenderer.cs Line 57
// FirstIndex = ListView.IndexOfChild(ViewGroup.Parent as View);
FirstIndex = ListView.GetPositionForView(ViewGroup.Parent as View);
and Line 72
// SecondIndex = ListView.IndexOfChild(ViewGroup.Parent as View);
SecondIndex = ListView.GetPositionForView(ViewGroup.Parent as View);
This comment has been removed by the author.
ReplyDeleteThis is working great in my Droid project, but the ItemLongClick event is overriding the ContextActions defined in my Xamarin.Forms page. If I comment out the Control.ItemLongClick += OnItemLongClick line, the Android context menu shows just fine.
ReplyDeleteOn iOS, dragging a cell to the side displays the context action menu items. And in Android, if I do not have the Control.ItemLongClick, it, too, displays the context menu as expected.
In the DraggableListViewRenderer I implemented based on your code, I added:
public override bool OnInterceptTouchEvent(MotionEvent ev)
{
_x = ev.RawX;
_y = ev.RawY;
return base.OnInterceptTouchEvent(ev);
}
And in OnItemLongClick, I've added a test in ItemLongClick to only start the drag if the touch event is x < 100, which is where I've placed a drag image. Somehow the event handler overrides the ability to display the context menu.
Did you ever do an iOS implementation of this?
ReplyDelete