Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,14 @@ protected virtual void BindTemplatedItemViewHolder(TemplatedItemViewHolder templ
templatedItemViewHolder.Bind(context, ItemsView);
}

/// <summary>
/// Clears any cached item size used by the MeasureFirstItem sizing strategy.
/// Called when the RecyclerView's size changes (e.g., after an orientation change).
/// </summary>
internal virtual void ClearMeasureCache()
{
}

void UpdateUsingItemTemplate()
{
_usingItemTemplate = ItemsView.ItemTemplate != null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,5 +169,10 @@ void SetStaticSize(Size size)
{
_size = size;
}

internal override void ClearMeasureCache()
{
_size = null;
}
}
}
11 changes: 11 additions & 0 deletions src/Controls/src/Core/Handlers/Items/Android/ItemContentView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ internal void HandleItemSizingStrategy(Action<Size> reportMeasure, Size? size)
_pixelSize = size;
}

/// <summary>
/// Invalidates the cached size so the next measure pass will re-measure the content.
/// Called when the parent RecyclerView's size changes (e.g., after orientation change).
/// </summary>
internal void InvalidateCachedSize()
{
_pixelSize = null;
_previousPixelWidth = -1;
_previousPixelHeight = -1;
}

protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
if (Content == null)
Expand Down
29 changes: 29 additions & 0 deletions src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,35 @@ protected override void OnLayout(bool changed, int l, int t, int r, int b)
_scrollHelper?.AdjustScroll();
}

protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);

// When the RecyclerView's size changes (e.g., after an orientation change while
// the CollectionView was not visible), we need to invalidate all visible item views
// to ensure they are re-measured with the new dimensions.
if (oldw > 0 && oldh > 0 && (Math.Abs(w - oldw) > 1 || Math.Abs(h - oldh) > 1))
{
InvalidateItemMeasures();
}
}

void InvalidateItemMeasures()
{
// Clear the adapter's static size cache (used by MeasureFirstItem strategy)
ItemsViewAdapter?.ClearMeasureCache();

// Force all visible children to invalidate their cached sizes and re-measure
for (int i = 0; i < ChildCount; i++)
{
if (GetChildAt(i) is ItemContentView itemContentView)
{
itemContentView.InvalidateCachedSize();
itemContentView.ForceLayout();
}
}
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ override Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRenderer.OnH
~override Microsoft.Maui.Controls.Handlers.Items.RecyclerViewScrollListener<TItemsView, TItemsViewSource>.OnScrollStateChanged(AndroidX.RecyclerView.Widget.RecyclerView recyclerView, int newState) -> void
~override Microsoft.Maui.Controls.Handlers.Items.SelectableItemsViewAdapter<TItemsView, TItemsSource>.IsSelectionEnabled(Android.Views.ViewGroup parent, int viewType) -> bool
override Microsoft.Maui.Controls.TitleBar.OnBindingContextChanged() -> void
override Microsoft.Maui.Controls.Handlers.Items.MauiRecyclerView<TItemsView, TAdapter, TItemsViewSource>.OnSizeChanged(int w, int h, int oldw, int oldh) -> void
53 changes: 53 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue19667.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 19667, "CollectionView contents not sizing correctly after orientation change", PlatformAffected.Android)]
public class Issue19667 : TestShell
{
protected override void Init()
{
ContentPage page1 = new ContentPage
{
Content = new Label
{
Text = "Page 1",
AutomationId = "Page1Label",
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}
};

var items = Enumerable.Range(0, 15).ToList();

CollectionView collectionView = new CollectionView

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[moderate] Regression Prevention and Test Coverage - The production fix adds ClearMeasureCache() specifically for the Android MeasureFirstItem static-size cache, but this test leaves CollectionView.ItemSizingStrategy at its default (MeasureAllItems). That means the regression only exercises the visible-child ForceLayout() path and does not cover the adapter _size cache invalidation added in this PR. Please set ItemSizingStrategy = ItemSizingStrategy.MeasureFirstItem here, or add a second case, so the test would fail if StructuredItemsViewAdapter.ClearMeasureCache() regresses.

{
AutomationId = "CollectionView19667",
ItemsSource = items,
ItemTemplate = new DataTemplate(() =>
{
Label label = new Label
{
VerticalTextAlignment = TextAlignment.Center
};
label.SetBinding(Label.TextProperty, new Binding(".", stringFormat: "Item {0}"));
label.SetBinding(AutomationIdProperty, new Binding(".", stringFormat: "CvItem{0}"));

Grid grid = new Grid
{
BackgroundColor = Colors.Beige,
Padding = new Thickness(20),
Margin = new Thickness(5, 2)
};
grid.Add(label);
return grid;
})
};

ContentPage page2 = new ContentPage
{
Content = collectionView
};

AddFlyoutItem(page1, "Page1");
AddFlyoutItem(page2, "CollectionViewPage");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#if ANDROID || IOS // The test fails on Windows and MacCatalyst because the SetOrientation method, which is intended to change the device orientation, is only supported on mobile platforms iOS and Android.
Comment thread
SyedAbdulAzeemSF4852 marked this conversation as resolved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[moderate] UI Test Applicability - This regression is for an Android-only handler fix and the HostApp issue page is marked PlatformAffected.Android, but the NUnit test also compiles for iOS. An iOS run would validate unrelated platform behavior and can create noise without covering the changed Android MauiRecyclerView code. Please scope this test to #if ANDROID unless there is an intentional iOS fix being validated too.

using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue19667 : _IssuesUITest
{
public Issue19667(TestDevice device) : base(device)
{
}

public override string Issue => "CollectionView contents not sizing correctly after orientation change";

[Test]
[Category(UITestCategories.CollectionView)]
public void CollectionViewItemsSizeCorrectlyAfterOrientationChange()
{
App.TapShellFlyoutIcon();
App.Tap("CollectionViewPage");
App.WaitForElement("CollectionView19667");
App.WaitForElement("CvItem0");

var portraitWidth = App.WaitForElement("CvItem0").GetRect().Width;

App.TapShellFlyoutIcon();
App.Tap("Page1");
App.WaitForElement("Page1Label");

App.SetOrientationLandscape();

App.TapShellFlyoutIcon();
App.Tap("CollectionViewPage");
App.WaitForElement("CollectionView19667");
App.WaitForElement("CvItem0");

var landscapeWidth = App.WaitForElement("CvItem0").GetRect().Width;
Assert.That(landscapeWidth, Is.GreaterThan(portraitWidth),
"CollectionView items should resize to landscape width after orientation change.");
}

[TearDown]
public void TearDown()
{
App.SetOrientationPortrait();
}
}
#endif
Loading