Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 68 additions & 27 deletions src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
namespace Microsoft.Maui.Controls.Handlers.Items
{

public class MauiRecyclerView<TItemsView, TAdapter, TItemsViewSource> : RecyclerView, IMauiRecyclerView<TItemsView>
public class MauiRecyclerView<TItemsView, TAdapter, TItemsViewSource> : RecyclerView, IMauiRecyclerView<TItemsView>, IMauiRecyclerView
where TItemsView : ItemsView
where TAdapter : ItemsViewAdapter<TItemsView, TItemsViewSource>
where TItemsViewSource : IItemsViewSource
Expand Down Expand Up @@ -84,11 +84,8 @@ public virtual void TearDownOldElement(TItemsView oldElement)
ItemsViewAdapter?.Dispose();
}

if (_snapManager != null)
{
_snapManager.Dispose();
_snapManager = null;
}
_snapManager?.Dispose();
_snapManager = null;

if (_itemDecoration != null)
{
Expand All @@ -102,11 +99,8 @@ public virtual void TearDownOldElement(TItemsView oldElement)
_itemTouchHelper = null;
}

if (_itemTouchHelperCallback != null)
{
_itemTouchHelperCallback.Dispose();
_itemTouchHelperCallback = null;
}
_itemTouchHelperCallback?.Dispose();
_itemTouchHelperCallback = null;
}

public virtual void SetUpNewElement(TItemsView newElement)
Expand Down Expand Up @@ -276,22 +270,26 @@ public virtual void UpdateCanReorderItems()
_itemTouchHelper.Dispose();
_itemTouchHelper = null;
}
if (_itemTouchHelperCallback != null)
{
_itemTouchHelperCallback.Dispose();
_itemTouchHelperCallback = null;
}

_itemTouchHelperCallback?.Dispose();
_itemTouchHelperCallback = null;
}
}

public virtual void UpdateLayoutManager()
{
_layoutPropertyChangedProxy?.Unsubscribe();
var itemsLayout = _getItemsLayout();

if (itemsLayout == ItemsLayout)
{
return;
}

ItemsLayout = _getItemsLayout();
_layoutPropertyChangedProxy?.Unsubscribe();
ItemsLayout = itemsLayout;

// Keep track of the ItemsLayout's property changes
if (ItemsLayout != null)
if (ItemsLayout is not null)
{
_layoutPropertyChanged ??= LayoutPropertyChanged;
_layoutPropertyChangedProxy = new WeakNotifyPropertyChangedProxy(ItemsLayout, _layoutPropertyChanged);
Expand Down Expand Up @@ -427,9 +425,9 @@ protected virtual int DetermineTargetPosition(ScrollToRequestEventArgs args)
if (args.Mode == ScrollToMode.Position)
{
// Do not use `IGroupableItemsViewSource` since `UngroupedItemsSource` also implements that interface
if (ItemsViewAdapter.ItemsSource is UngroupedItemsSource)
if (ItemsViewAdapter.ItemsSource is UngroupedItemsSource ungroupedSource)
{
return args.Index;
return ungroupedSource.HasHeader ? args.Index + 1 : args.Index;
}
else if (ItemsViewAdapter.ItemsSource is IGroupableItemsViewSource groupItemSource)
{
Expand Down Expand Up @@ -473,12 +471,26 @@ protected virtual void UpdateItemSpacing()

if (_itemDecoration is SpacingItemDecoration spacingDecoration)
{
// SpacingItemDecoration applies spacing to all items & all 4 sides of the items.
// We need to adjust the padding on the RecyclerView so this spacing isn't visible around the outer edge of our control.
// Horizontal & vertical spacing should only exist between items.
var horizontalPadding = -spacingDecoration.HorizontalOffset;
var verticalPadding = -spacingDecoration.VerticalOffset;
SetPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
// SpacingItemDecoration now removes spacing on outer edges (first/last row or column),
// so we only need negative padding on the cross-axis for grid layouts to compensate
// for the spacing between columns (vertical grid) or rows (horizontal grid).
if (ItemsLayout is GridItemsLayout gridItemsLayout)
{
if (gridItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal)
{
var verticalPadding = -spacingDecoration.VerticalOffset;
SetPadding(0, verticalPadding, 0, verticalPadding);
}
else
{
var horizontalPadding = -spacingDecoration.HorizontalOffset;
SetPadding(horizontalPadding, 0, horizontalPadding, 0);
}
}
else
{
SetPadding(0, 0, 0, 0);
}
}
}

Expand Down Expand Up @@ -513,6 +525,7 @@ protected virtual void LayoutPropertyChanged(object sender, PropertyChangedEvent
if (GetLayoutManager() is GridLayoutManager gridLayoutManager)
{
gridLayoutManager.SpanCount = ((GridItemsLayout)ItemsLayout).Span;
UpdateItemSpacing();
}
}
else if (propertyChanged.IsOneOf(Microsoft.Maui.Controls.ItemsLayout.SnapPointsTypeProperty, Microsoft.Maui.Controls.ItemsLayout.SnapPointsAlignmentProperty))
Expand Down Expand Up @@ -585,6 +598,34 @@ internal void UpdateEmptyViewVisibility()
SwapAdapter(ItemsViewAdapter, true);
UpdateLayoutManager();
}
else if (showEmptyView && currentAdapter == _emptyViewAdapter)
{
if (ShouldUpdateEmptyView())
{
// Header/footer properties changed - detach and reattach adapter to force RecyclerView to recalculate the positions.
SetAdapter(null);
SwapAdapter(_emptyViewAdapter, true);
UpdateEmptyView();
}
}
}

bool ShouldUpdateEmptyView()
{
if (ItemsView is StructuredItemsView structuredItemsView)
{
if (_emptyViewAdapter.Header != structuredItemsView.Header ||
_emptyViewAdapter.HeaderTemplate != structuredItemsView.HeaderTemplate ||
_emptyViewAdapter.Footer != structuredItemsView.Footer ||
_emptyViewAdapter.FooterTemplate != structuredItemsView.FooterTemplate ||
_emptyViewAdapter.EmptyView != ItemsView.EmptyView ||
_emptyViewAdapter.EmptyViewTemplate != ItemsView.EmptyViewTemplate)
{
return true;
}
}

return false;
}

internal void AdjustScrollForItemUpdate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ public class SpacingItemDecoration : RecyclerView.ItemDecoration

public int VerticalOffset { get; }

int _span = 1;

ItemsLayoutOrientation _orientation;

public SpacingItemDecoration(Context context, IItemsLayout itemsLayout)
{
// The original "SpacingItemDecoration" applied spacing based on an item's current span index.
Expand All @@ -35,6 +39,8 @@ public SpacingItemDecoration(Context context, IItemsLayout itemsLayout)
case GridItemsLayout gridItemsLayout:
horizontalOffset = gridItemsLayout.HorizontalItemSpacing / 2.0;
verticalOffset = gridItemsLayout.VerticalItemSpacing / 2.0;
_span = gridItemsLayout.Span;
_orientation = gridItemsLayout.Orientation;
break;
case LinearItemsLayout listItemsLayout:
if (listItemsLayout.Orientation == ItemsLayoutOrientation.Horizontal)
Expand All @@ -47,10 +53,12 @@ public SpacingItemDecoration(Context context, IItemsLayout itemsLayout)
horizontalOffset = 0;
verticalOffset = listItemsLayout.ItemSpacing / 2.0;
}
_orientation = listItemsLayout.Orientation;
break;
default:
horizontalOffset = 0;
verticalOffset = 0;
_orientation = ItemsLayoutOrientation.Vertical;
break;
}

Expand All @@ -62,10 +70,39 @@ public override void GetItemOffsets(ARect outRect, AView view, RecyclerView pare
{
base.GetItemOffsets(outRect, view, parent, state);

int position = parent.GetChildAdapterPosition(view);
if (position == RecyclerView.NoPosition)
return;

int itemCount = state.ItemCount;
if (itemCount <= 0)
return;

outRect.Left = HorizontalOffset;
outRect.Right = HorizontalOffset;
outRect.Bottom = VerticalOffset;
outRect.Top = VerticalOffset;

// Remove spacing on the outer edges so spacing only appears between items.
// A linear layout is effectively span=1, so the same math works for both.
int rowCol = _span <= 1 ? position : position / _span;
int totalRowsCols = _span <= 1 ? itemCount : (itemCount + _span - 1) / _span;
int lastRowCol = totalRowsCols - 1;

if (_orientation == ItemsLayoutOrientation.Vertical)
{
if (rowCol == 0)
outRect.Top = 0;
if (rowCol == lastRowCol)
outRect.Bottom = 0;
}
else
{
if (rowCol == 0)
outRect.Left = 0;
if (rowCol == lastRowCol)
outRect.Right = 0;
}
}
}
}
Loading