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(ElementChangedEventArgse) { 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