Skip to content

Commit

Permalink
fix(listview): [Android] Fix errors when dragging rapidly
Browse files Browse the repository at this point in the history
Before scroll-while-dragging, allow layout state to 'catch up' with pointer position if the drag outpaced the relayouting.

Fix an issue surfaced by this change where the reorder state was not properly cleaned up if the drag was released while the pointer was not over the list.
  • Loading branch information
davidjohnoliver committed Sep 17, 2021
1 parent 4a49f7d commit 1d94cf8
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 6 deletions.
19 changes: 15 additions & 4 deletions src/Uno.UI/UI/Xaml/Controls/ListViewBase/ListViewBase.DragDrop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ private static void OnCanDragItemsChanged(DependencyObject snd, DependencyProper
items.ForEach(ClearContainerForDragDrop);
}
}
}
}
#endregion

private void PrepareContainerForDragDrop(UIElement itemContainer)
Expand Down Expand Up @@ -134,7 +134,7 @@ private static void OnItemContainerDragStarting(UIElement sender, DragStartingEv

// The ListView must have both CanReorderItems and AllowDrop flags set to allow re-ordering (UWP)
// We also do not allow re-ordering if we where not able to find the item (as it has to be hidden in the view) (Uno only)
if (that.CanReorderItems && that.AllowDrop && draggedItem is {})
if (that.CanReorderItems && that.AllowDrop && draggedItem is { })
{
args.Data.SetData(ReorderOwnerFormatId, that);
args.Data.SetData(ReorderItemFormatId, draggedItem);
Expand Down Expand Up @@ -176,6 +176,10 @@ private static void OnItemContainerDragCompleted(UIElement sender, DropCompleted

that.DragItemsCompleted?.Invoke(that, args);
}

// Normally this will have been done by OnReorderCompleted, but sometimes OnReorderCompleted may not be called
// (eg if drag was released outside bounds of list)
that.CleanupReordering();
}
}

Expand Down Expand Up @@ -294,7 +298,7 @@ void ProcessMove(
// If we've moved items down, we have to take in consideration that the updatedIndex
// is already assuming that the item has been removed, so it's offsetted by 1.
newIndex--;
}
}
#endif
}

Expand Down Expand Up @@ -341,9 +345,16 @@ void ProcessMove(
private void UpdateReordering(Point location, FrameworkElement draggedContainer, object draggedItem)
=> VirtualizingPanel?.GetLayouter().UpdateReorderingItem(location, draggedContainer, draggedItem);

Uno.UI.IndexPath? CompleteReordering(FrameworkElement draggedContainer, object draggedItem)
private Uno.UI.IndexPath? CompleteReordering(FrameworkElement draggedContainer, object draggedItem)
=> VirtualizingPanel?.GetLayouter().CompleteReorderingItem(draggedContainer, draggedItem);

private void CleanupReordering()
#if __ANDROID__
=> VirtualizingPanel?.GetLayouter().CleanupReordering();
#else
{ }
#endif

#region Helpers
private static bool IsObservableCollection(object src)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,10 @@ protected void AddView(View child, GeneratorDirection direction, ViewType viewTy
private int ScrollBy(int offset, RecyclerView.Recycler recycler, RecyclerView.State state)
{
var fillDirection = offset >= 0 ? GeneratorDirection.Forward : GeneratorDirection.Backward;
if (IsReorderingAndNotReadyToScroll(fillDirection))
{
return 0;
}
int unconsumedOffset = offset;
int actualOffset = 0;
int appliedOffset = 0;
Expand Down Expand Up @@ -1092,6 +1096,15 @@ private int ScrollBy(int offset, RecyclerView.Recycler recycler, RecyclerView.St
return actualOffset;
}

/// <summary>
/// During a drag-to-reorder, if the item is dragged very rapidly then we may receive a scroll request before the item has been
/// redrawn at the position under the cursor. If the item is still at the beginning of the list (that is, the 'trailing' position
/// relative to scroll), this would violate the assumptions of the reordering logic (<see cref="TryTrimReorderingView(GeneratorDirection, RecyclerView.Recycler)"/>).
/// As a simple fix we simply skip this scroll request, and wait for one to occur after the item has been repositioned.
/// </summary>
private bool IsReorderingAndNotReadyToScroll(GeneratorDirection fillDirection)
=> _pendingReorder?.index is { } reorderingIndex && reorderingIndex == GetTrailingLine(fillDirection).FirstItem;

private int GetScrollConsumptionIncrement(GeneratorDirection fillDirection)
{
if (ItemViewCount > 0)
Expand Down Expand Up @@ -2150,14 +2163,23 @@ internal void UpdateReorderingItem(Windows.Foundation.Point location, FrameworkE
_shouldDecrementSeedForPendingReorder = true;
}
}

CleanupReordering();

return updatedIndex;
}

/// <summary>
/// Clean up state after a drag-to-reorder operation.
/// </summary>
internal void CleanupReordering()
{
_pendingReorder = null;

ViewCache.RemoveReorderingItem();
// We need a full refresh to properly re-arrange all items at their right location,
// ignoring the temp location of the dragged / reordered item.
RecycleLayout();

return updatedIndex;
}

protected bool ShouldInsertReorderingView(GeneratorDirection direction, double physicalExtentOffset)
Expand Down

0 comments on commit 1d94cf8

Please sign in to comment.