Skip to content

Refactor ItemsLayout handling: dynamic default + virtual view-managed subscriptions #29638

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
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
49 changes: 15 additions & 34 deletions src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ public class MauiRecyclerView<TItemsView, TAdapter, TItemsViewSource> : Recycler

ItemTouchHelper _itemTouchHelper;
SimpleItemTouchHelperCallback _itemTouchHelperCallback;
WeakNotifyPropertyChangedProxy _layoutPropertyChangedProxy;
PropertyChangedEventHandler _layoutPropertyChanged;

~MauiRecyclerView() => _layoutPropertyChangedProxy?.Unsubscribe();
//TODO: Remove this in .NET 10
~MauiRecyclerView() {}

public MauiRecyclerView(Context context, Func<IItemsLayout> getItemsLayout, Func<TAdapter> getAdapter) : base(new ContextThemeWrapper(context, Resource.Style.collectionViewTheme))
{
Expand All @@ -59,13 +58,6 @@ public MauiRecyclerView(Context context, Func<IItemsLayout> getItemsLayout, Func

public virtual void TearDownOldElement(TItemsView oldElement)
{
// Stop listening for layout property changes
if (_layoutPropertyChangedProxy is not null)
{
_layoutPropertyChangedProxy.Unsubscribe();
_layoutPropertyChanged = null;
}

// Stop listening for ScrollTo requests
oldElement.ScrollToRequested -= ScrollToRequested;

Expand Down Expand Up @@ -286,21 +278,12 @@ public virtual void UpdateCanReorderItems()

public virtual void UpdateLayoutManager()
{
_layoutPropertyChangedProxy?.Unsubscribe();

ItemsLayout = _getItemsLayout();

// Keep track of the ItemsLayout's property changes
if (ItemsLayout != null)
{
_layoutPropertyChanged ??= LayoutPropertyChanged;
_layoutPropertyChangedProxy = new WeakNotifyPropertyChangedProxy(ItemsLayout, _layoutPropertyChanged);
}

SetLayoutManager(SelectLayoutManager(ItemsLayout));

UpdateFlowDirection();
UpdateItemSpacing();
UpdateItemsLayoutProperties();
}

protected virtual RecyclerViewScrollListener<TItemsView, TItemsViewSource> CreateScrollListener() => new(ItemsView, ItemsViewAdapter);
Expand Down Expand Up @@ -506,24 +489,22 @@ protected virtual void ScrollToRequested(object sender, ScrollToRequestEventArgs
ScrollTo(args);
}

//TODO: Remove this in .NET 10
protected virtual void LayoutPropertyChanged(object sender, PropertyChangedEventArgs propertyChanged)
{
if (propertyChanged.Is(GridItemsLayout.SpanProperty))
{
if (GetLayoutManager() is GridLayoutManager gridLayoutManager)
{
gridLayoutManager.SpanCount = ((GridItemsLayout)ItemsLayout).Span;
}
}
else if (propertyChanged.IsOneOf(Microsoft.Maui.Controls.ItemsLayout.SnapPointsTypeProperty, Microsoft.Maui.Controls.ItemsLayout.SnapPointsAlignmentProperty))
{
UpdateSnapBehavior();
}
else if (propertyChanged.IsOneOf(LinearItemsLayout.ItemSpacingProperty,
GridItemsLayout.HorizontalItemSpacingProperty, GridItemsLayout.VerticalItemSpacingProperty))

}

void UpdateItemsLayoutProperties()
{
if (ItemsLayout is GridItemsLayout gridItemsLayout && GetLayoutManager() is GridLayoutManager gridLayoutManager)
{
UpdateItemSpacing();
gridLayoutManager.SpanCount = gridItemsLayout.Span;
}

UpdateSnapBehavior();

UpdateItemSpacing();
}

protected override void OnLayout(bool changed, int l, int t, int r, int b)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,6 @@ protected virtual void UpdateItemsLayout()
_defaultVerticalScrollVisibility = null;

UpdateItemTemplate();
UpdateItemsSource();
UpdateScrollBarVisibility();
UpdateEmptyView();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,27 @@ public partial class StructuredItemsViewHandler<TItemsView> : ItemsViewHandler<T
{
View _currentHeader;
View _currentFooter;
WeakNotifyPropertyChangedProxy _layoutPropertyChangedProxy;
PropertyChangedEventHandler _layoutPropertyChanged;

~StructuredItemsViewHandler() => _layoutPropertyChangedProxy?.Unsubscribe();
//TODO: Remove this in .NET 10
~StructuredItemsViewHandler() { }

protected override IItemsLayout Layout { get => ItemsView?.ItemsLayout; }

//TODO: Remove this in .NET 10
protected override void ConnectHandler(ListViewBase platformView)
{
base.ConnectHandler(platformView);

if (Layout is not null)
{
_layoutPropertyChanged ??= LayoutPropertyChanged;
_layoutPropertyChangedProxy = new WeakNotifyPropertyChangedProxy(Layout, _layoutPropertyChanged);
}
else if (_layoutPropertyChangedProxy is not null)
{
_layoutPropertyChangedProxy.Unsubscribe();
_layoutPropertyChangedProxy = null;
}
}

//TODO: Remove this in .NET 10
protected override void DisconnectHandler(ListViewBase platformView)
{
base.DisconnectHandler(platformView);

if (_layoutPropertyChangedProxy is not null)
{
_layoutPropertyChangedProxy.Unsubscribe();
_layoutPropertyChangedProxy = null;
}
}

void LayoutPropertyChanged(object sender, PropertyChangedEventArgs e)
void UpdateItemsLayoutProperties()
{
if (e.PropertyName == GridItemsLayout.SpanProperty.PropertyName)
UpdateItemsLayoutSpan();
else if (e.PropertyName == GridItemsLayout.HorizontalItemSpacingProperty.PropertyName || e.PropertyName == GridItemsLayout.VerticalItemSpacingProperty.PropertyName)
UpdateItemsLayoutItemSpacing();
else if (e.PropertyName == LinearItemsLayout.ItemSpacingProperty.PropertyName)
UpdateItemsLayoutSpan();
UpdateItemsLayoutItemSpacing();
}

Expand All @@ -73,6 +53,7 @@ public static void MapFooterTemplate(StructuredItemsViewHandler<TItemsView> hand
public static void MapItemsLayout(StructuredItemsViewHandler<TItemsView> handler, StructuredItemsView itemsView)
{
handler.UpdateItemsLayout();
handler.UpdateItemsLayoutProperties();
}

public static void MapItemSizingStrategy(StructuredItemsViewHandler<TItemsView> handler, StructuredItemsView itemsView)
Expand Down Expand Up @@ -298,13 +279,13 @@ void UpdateItemsLayoutItemSpacing()

if (Layout is LinearItemsLayout linearItemsLayout)
{
switch (ListViewBase)
switch (linearItemsLayout.Orientation)
{
case FormsListView formsListView:
formsListView.ItemContainerStyle = GetVerticalItemContainerStyle(linearItemsLayout);
case ItemsLayoutOrientation.Vertical:
ListViewBase.ItemContainerStyle = GetVerticalItemContainerStyle(linearItemsLayout);
break;
case WListView listView:
listView.ItemContainerStyle = GetHorizontalItemContainerStyle(linearItemsLayout);
case ItemsLayoutOrientation.Horizontal:
ListViewBase.ItemContainerStyle = GetHorizontalItemContainerStyle(linearItemsLayout);
break;
}
}
Expand Down
18 changes: 15 additions & 3 deletions src/Controls/src/Core/Handlers/Items/iOS/CarouselViewLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,25 @@ namespace Microsoft.Maui.Controls.Handlers.Items
public class CarouselViewLayout : ItemsViewLayout
{
readonly WeakReference<CarouselView> _carouselView;
readonly ItemsLayout _itemsLayout;
readonly WeakReference<ItemsLayout> _itemsLayout;
ItemsLayout ItemsLayout
{
get
{
_itemsLayout.TryGetTarget(out var itemsLayout);
return itemsLayout;
}
set
{
_itemsLayout.SetTarget(value);
}
}
CGPoint? _pendingOffset;

public CarouselViewLayout(ItemsLayout itemsLayout, CarouselView carouselView) : base(itemsLayout)
{
_carouselView = new(carouselView);
_itemsLayout = itemsLayout;
_itemsLayout = new(itemsLayout);
}

public override void ConstrainTo(CGSize size)
Expand All @@ -38,7 +50,7 @@ public override void ConstrainTo(CGSize size)

public override nfloat GetMinimumInteritemSpacingForSection(UICollectionView collectionView, UICollectionViewLayout layout, nint section)
{
if (_itemsLayout is LinearItemsLayout linearItemsLayout)
if (ItemsLayout is LinearItemsLayout linearItemsLayout)
return (nfloat)linearItemsLayout.ItemSpacing;

return base.GetMinimumInteritemSpacingForSection(collectionView, layout, section);
Expand Down
42 changes: 29 additions & 13 deletions src/Controls/src/Core/Handlers/Items/iOS/GridViewLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,24 @@ namespace Microsoft.Maui.Controls.Handlers.Items
{
public class GridViewLayout : ItemsViewLayout
{
readonly GridItemsLayout _itemsLayout;
readonly WeakReference<GridItemsLayout> _itemsLayout = new WeakReference<GridItemsLayout>(null);

GridItemsLayout ItemsLayout
{
get
{
_itemsLayout.TryGetTarget(out var itemsLayout);
return itemsLayout;
}
set
{
_itemsLayout.SetTarget(value);
}
}

public GridViewLayout(GridItemsLayout itemsLayout, ItemSizingStrategy itemSizingStrategy) : base(itemsLayout, itemSizingStrategy)
{
_itemsLayout = itemsLayout;
ItemsLayout = itemsLayout;
}

protected override void HandlePropertyChanged(PropertyChangedEventArgs propertyChanged)
Expand All @@ -34,18 +47,19 @@ protected override void HandlePropertyChanged(PropertyChangedEventArgs propertyC

public override void ConstrainTo(CGSize size)
{
var itemsLayout = ItemsLayout;
var availableSpace = ScrollDirection == UICollectionViewScrollDirection.Vertical
? size.Width : size.Height;

var spacing = (nfloat)(ScrollDirection == UICollectionViewScrollDirection.Vertical
? _itemsLayout.HorizontalItemSpacing
: _itemsLayout.VerticalItemSpacing);
? itemsLayout.HorizontalItemSpacing
: itemsLayout.VerticalItemSpacing);

spacing = ReduceSpacingToFitIfNeeded(availableSpace, spacing, _itemsLayout.Span);
spacing = ReduceSpacingToFitIfNeeded(availableSpace, spacing, itemsLayout.Span);

spacing *= (_itemsLayout.Span - 1);
spacing *= (itemsLayout.Span - 1);

ConstrainedDimension = (availableSpace - spacing) / _itemsLayout.Span;
ConstrainedDimension = (availableSpace - spacing) / itemsLayout.Span;

// We need to truncate the decimal part of ConstrainedDimension
// or we occasionally run into situations where the rows/columns don't fit
Expand Down Expand Up @@ -194,22 +208,23 @@ public override UICollectionViewLayoutInvalidationContext GetInvalidationContext

public override nfloat GetMinimumInteritemSpacingForSection(UICollectionView collectionView, UICollectionViewLayout layout, nint section)
{
var itemsLayout = ItemsLayout;
var requestedSpacing = ScrollDirection == UICollectionViewScrollDirection.Horizontal
? (nfloat)_itemsLayout.VerticalItemSpacing
: (nfloat)_itemsLayout.HorizontalItemSpacing;
? (nfloat)itemsLayout.VerticalItemSpacing
: (nfloat)itemsLayout.HorizontalItemSpacing;

var availableSpace = ScrollDirection == UICollectionViewScrollDirection.Horizontal
? collectionView.Frame.Height
: collectionView.Frame.Width;

return ReduceSpacingToFitIfNeeded(availableSpace, requestedSpacing, _itemsLayout.Span);
return ReduceSpacingToFitIfNeeded(availableSpace, requestedSpacing, itemsLayout.Span);
}

void CenterAlignCellsInColumn(UICollectionViewLayoutAttributes preferredAttributes)
{
// Determine the set of cells above this one
var index = preferredAttributes.IndexPath;
var span = _itemsLayout.Span;
var span = ItemsLayout.Span;

var column = index.Item / span;
var start = (int)column * span;
Expand Down Expand Up @@ -257,6 +272,7 @@ bool NeedsSingleItemHorizontalAlignmentAdjustment(UICollectionViewLayoutAttribut

bool NeedsPartialColumnAdjustment(int section = 0)
{
var itemsLayout = ItemsLayout;
if (ScrollDirection == UICollectionViewScrollDirection.Vertical)
{
// The bug only occurs with Horizontal scrolling
Expand All @@ -280,13 +296,13 @@ bool NeedsPartialColumnAdjustment(int section = 0)

var itemCount = CollectionView.NumberOfItemsInSection(section);

if (itemCount < _itemsLayout.Span)
if (itemCount < itemsLayout.Span)
{
// If there is just one partial column, no problem; UICollectionViewFlowLayout gets it right
return false;
}

if (itemCount % _itemsLayout.Span == 0)
if (itemCount % itemsLayout.Span == 0)
{
// All of the columns are full; the bug only occurs when we have a partial column
return false;
Expand Down
Loading