diff --git a/src/Controls/src/Core/Handlers/Items/Android/Adapters/ItemsViewAdapter.cs b/src/Controls/src/Core/Handlers/Items/Android/Adapters/ItemsViewAdapter.cs
index ca7566c43543..063a426011fc 100644
--- a/src/Controls/src/Core/Handlers/Items/Android/Adapters/ItemsViewAdapter.cs
+++ b/src/Controls/src/Core/Handlers/Items/Android/Adapters/ItemsViewAdapter.cs
@@ -155,6 +155,14 @@ protected virtual void BindTemplatedItemViewHolder(TemplatedItemViewHolder templ
templatedItemViewHolder.Bind(context, ItemsView);
}
+ ///
+ /// Clears any cached item size used by the MeasureFirstItem sizing strategy.
+ /// Called when the RecyclerView's size changes (e.g., after an orientation change).
+ ///
+ internal virtual void ClearMeasureCache()
+ {
+ }
+
void UpdateUsingItemTemplate()
{
_usingItemTemplate = ItemsView.ItemTemplate != null;
diff --git a/src/Controls/src/Core/Handlers/Items/Android/Adapters/StructuredItemsViewAdapter.cs b/src/Controls/src/Core/Handlers/Items/Android/Adapters/StructuredItemsViewAdapter.cs
index 42c8055d9a73..109c2681cff6 100644
--- a/src/Controls/src/Core/Handlers/Items/Android/Adapters/StructuredItemsViewAdapter.cs
+++ b/src/Controls/src/Core/Handlers/Items/Android/Adapters/StructuredItemsViewAdapter.cs
@@ -169,5 +169,10 @@ void SetStaticSize(Size size)
{
_size = size;
}
+
+ internal override void ClearMeasureCache()
+ {
+ _size = null;
+ }
}
}
diff --git a/src/Controls/src/Core/Handlers/Items/Android/ItemContentView.cs b/src/Controls/src/Core/Handlers/Items/Android/ItemContentView.cs
index 2ff1b4116442..ffe46b104a4f 100644
--- a/src/Controls/src/Core/Handlers/Items/Android/ItemContentView.cs
+++ b/src/Controls/src/Core/Handlers/Items/Android/ItemContentView.cs
@@ -85,6 +85,17 @@ internal void HandleItemSizingStrategy(Action reportMeasure, Size? size)
_pixelSize = size;
}
+ ///
+ /// 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).
+ ///
+ 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)
diff --git a/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs b/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs
index 5e691e8eb63b..937902e8e722 100644
--- a/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs
+++ b/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs
@@ -680,6 +680,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)
{
if (disposing)
diff --git a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
index 7379bdbc0c06..ed768b9e0ee1 100644
--- a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
@@ -22,5 +22,6 @@ override Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRenderer.OnH
override Microsoft.Maui.Controls.SwipeItemView.IsEnabledCore.get -> bool
~override Microsoft.Maui.Controls.RadioButton.OnPropertyChanged(string propertyName = null) -> void
override Microsoft.Maui.Controls.TitleBar.OnBindingContextChanged() -> void
+override Microsoft.Maui.Controls.Handlers.Items.MauiRecyclerView.OnSizeChanged(int w, int h, int oldw, int oldh) -> void
~override Microsoft.Maui.Controls.SwipeItems.OnPropertyChanged(string propertyName = null) -> void
Microsoft.Maui.Controls.Label.~Label() -> void
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue19667.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue19667.cs
new file mode 100644
index 000000000000..273941164db6
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue19667.cs
@@ -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
+ {
+ 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");
+ }
+}
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue19667.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue19667.cs
new file mode 100644
index 000000000000..c317ccbb3da8
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue19667.cs
@@ -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.
+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